From efc5ea23bfcc6fb5ddc211e9a45f824ed751fc8f Mon Sep 17 00:00:00 2001 From: scholar <1145227973@qq.com> Date: Thu, 15 Aug 2024 20:29:17 +0800 Subject: [PATCH 01/42] =?UTF-8?q?=E5=AE=8C=E6=88=90todo=E9=83=A8=E5=88=86?= =?UTF-8?q?=201=EF=BC=8C=E5=8D=8E=E4=B8=BA=E4=BA=91=E7=9F=AD=E4=BF=A1?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E4=BC=98=E5=8C=96=EF=BC=8C=E5=8E=BB=E9=99=A4?= =?UTF-8?q?=E4=B8=8D=E5=BF=85=E8=A6=81VO;=202=EF=BC=8C=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=8D=8E=E4=B8=BA=E4=BA=91=E7=9F=AD=E4=BF=A1=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=8D=95=E6=B5=8B=EF=BC=9B=203=EF=BC=8Cfix=E5=8D=8E=E4=B8=BA?= =?UTF-8?q?=E4=BA=91=E7=9F=AD=E4=BF=A1=E6=8E=A5=E6=94=B6=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E5=9B=9E=E8=B0=83=E7=9A=84bug=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/sms/SmsCallbackController.java | 13 +- .../sms/core/client/impl/HuaweiSmsClient.java | 154 +++++++----------- .../core/client/impl/HuaweiSmsClientTest.java | 114 +++++++++++++ 3 files changed, 182 insertions(+), 99 deletions(-) create mode 100644 yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClientTest.java diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java index 0bb406710..90cb763cc 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java @@ -5,16 +5,17 @@ import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsChannelEnum; import cn.iocoder.yudao.module.system.service.sms.SmsSendService; +import com.xingyuv.captcha.util.StreamUtils; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import jakarta.annotation.Resource; import jakarta.annotation.security.PermitAll; import jakarta.servlet.http.HttpServletRequest; +import java.nio.charset.Charset; + import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @Tag(name = "管理后台 - 短信回调") @@ -50,10 +51,8 @@ public class SmsCallbackController { @PermitAll @Operation(summary = "华为云短信的回调", description = "参见 https://support.huaweicloud.com/api-msgsms/sms_05_0003.html 文档") @OperateLog(enable = false) - public CommonResult receiveHuaweiSmsStatus(HttpServletRequest request) throws Throwable { - String text = ServletUtils.getBody(request); - smsSendService.receiveSmsStatus(SmsChannelEnum.HUAWEI.getCode(), text); + public CommonResult receiveHuaweiSmsStatus(@RequestBody String requestBody) throws Throwable { + smsSendService.receiveSmsStatus(SmsChannelEnum.HUAWEI.getCode(), requestBody); return success(true); } - } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java index 4df820861..8465d0558 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java @@ -5,12 +5,11 @@ import cn.hutool.core.lang.Assert; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.SecureUtil; -import cn.hutool.http.HttpRequest; -import cn.hutool.http.HttpResponse; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import cn.iocoder.yudao.framework.common.core.KeyValue; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO; @@ -18,14 +17,14 @@ import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateR import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; import lombok.extern.slf4j.Slf4j; import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; import java.net.URLEncoder; import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.ZoneId; import java.util.*; @@ -33,9 +32,6 @@ import java.time.LocalDateTime; import static cn.hutool.crypto.digest.DigestUtil.sha256Hex; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT; /** @@ -47,9 +43,6 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DE @Slf4j public class HuaweiSmsClient extends AbstractSmsClient { - /** - * 调用成功 code - */ public static final String URL = "https://smsapi.cn-north-4.myhuaweicloud.com:443/sms/batchSendSms/v1";//APP接入地址+接口访问URI public static final String HOST = "smsapi.cn-north-4.myhuaweicloud.com:443"; public static final String SIGNEDHEADERS = "content-type;host;x-sdk-date"; @@ -78,13 +71,14 @@ public class HuaweiSmsClient extends AbstractSmsClient { List templateParas = CollectionUtils.convertList(templateParams, kv -> String.valueOf(kv.getValue())); - JSONObject JsonResponse = sendSmsRequest(sender,mobile,templateId,templateParas,statusCallBack); - SmsResponse smsResponse = getSmsSendResponse(JsonResponse); + JSONObject JsonResponse = request(sendLogId,sender,mobile,templateId,templateParas,statusCallBack); - return new SmsSendRespDTO().setSuccess(smsResponse.success).setApiMsg(smsResponse.data.toString()); + return new SmsSendRespDTO().setSuccess("000000".equals(JsonResponse.getStr("code"))) + .setSerialNo(JsonResponse.getJSONArray("result").getJSONObject(0).getStr("smsMsgId")) + .setApiCode(JsonResponse.getJSONArray("result").getJSONObject(0).getStr("status")); } - JSONObject sendSmsRequest(String sender,String mobile,String templateId,List templateParas,String statusCallBack) throws UnsupportedEncodingException { + JSONObject request(Long sendLogId,String sender,String mobile,String templateId,List templateParas,String statusCallBack) throws UnsupportedEncodingException { SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.ENGLISH); sdf.setTimeZone(TimeZone.getTimeZone("UTC")); @@ -97,8 +91,7 @@ public class HuaweiSmsClient extends AbstractSmsClient { String canonicalHeaders = "content-type:application/x-www-form-urlencoded\n" + "host:"+ HOST +"\n" + "x-sdk-date:" + sdkDate + "\n"; - //请求Body,不携带签名名称时,signature请填null - String body = buildRequestBody(sender, mobile, templateId, templateParas, statusCallBack, null); + String body = buildRequestBody(sender, mobile, templateId, templateParas, statusCallBack, sendLogId); if (null == body || body.isEmpty()) { return null; } @@ -118,26 +111,29 @@ public class HuaweiSmsClient extends AbstractSmsClient { + "SignedHeaders=" + SIGNEDHEADERS + ", " + "Signature=" + signature; // ************* 步骤 5:构造HttpRequest 并执行request请求,获得response ************* - HttpResponse response = HttpRequest.post(URL) - .header("Content-Type", "application/x-www-form-urlencoded") - .header("X-Sdk-Date", sdkDate) - .header("host",HOST) - .header("Authorization", authorization) - .body(body) - .execute(); + TreeMap headers = new TreeMap<>(); + headers.put("Content-Type", "application/x-www-form-urlencoded"); + headers.put("X-Sdk-Date", sdkDate); + headers.put("host", HOST); + headers.put("Authorization", authorization); - return JSONUtil.parseObj(response.body()); - } - - private SmsResponse getSmsSendResponse(JSONObject resJson) { - SmsResponse smsResponse = new SmsResponse(); - smsResponse.setSuccess("000000".equals(resJson.getStr("code"))); - smsResponse.setData(resJson); - return smsResponse; + String responseBody = HttpUtils.post(URL, headers, body); + return JSONUtil.parseObj(responseBody); +// +// +// HttpResponse response = HttpRequest.post(URL) +// .header("Content-Type", "application/x-www-form-urlencoded") +// .header("X-Sdk-Date", sdkDate) +// .header("host",HOST) +// .header("Authorization", authorization) +// .body(body) +// .execute(); +// +// return JSONUtil.parseObj(response.body()); } static String buildRequestBody(String sender, String receiver, String templateId, List templateParas, - String statusCallBack, String signature) throws UnsupportedEncodingException { + String statusCallBack, Long sendLogId) throws UnsupportedEncodingException { if (null == sender || null == receiver || null == templateId || sender.isEmpty() || receiver.isEmpty() || templateId.isEmpty()) { System.out.println("buildRequestBody(): sender, receiver or templateId is null."); @@ -150,7 +146,9 @@ public class HuaweiSmsClient extends AbstractSmsClient { appendToBody(body, "&templateId=", templateId); appendToBody(body, "&templateParas=", JsonUtils.toJsonString(templateParas)); appendToBody(body, "&statusCallback=", statusCallBack); - appendToBody(body, "&signature=", signature); + appendToBody(body, "&signature=", null); + appendToBody(body, "&extend=", String.valueOf(sendLogId)); + return body.toString(); } @@ -160,12 +158,35 @@ public class HuaweiSmsClient extends AbstractSmsClient { } } @Override - public List parseSmsReceiveStatus(String text) { - List statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class); - return convertList(statuses, status -> new SmsReceiveRespDTO().setSuccess(Objects.equals(status.getStatus(),"DELIVRD")) - .setErrorCode(status.getStatus()).setErrorMsg(status.getStatus()) - .setMobile(status.getPhoneNumber()).setReceiveTime(status.getUpdateTime()) - .setSerialNo(status.getSmsMsgId())); + public List parseSmsReceiveStatus(String requestBody) { + + System.out.println("text in parseSmsReceiveStatus===== " + requestBody); + + Map params = new HashMap<>(); + try { + String[] pairs = requestBody.split("&"); + for (String pair : pairs) { + int idx = pair.indexOf("="); + String key = URLDecoder.decode(pair.substring(0, idx), "UTF-8"); + String value = URLDecoder.decode(pair.substring(idx + 1), "UTF-8"); + params.put(key, value); + } + } catch (Exception e) { + e.printStackTrace(); + } + + List respDTOS = new ArrayList<>(); + respDTOS.add(new SmsReceiveRespDTO() + .setSuccess("DELIVRD".equals(params.get("status"))) // 是否接收成功 + .setErrorCode(params.get("status")) // 状态报告编码 + .setErrorMsg(params.get("statusDesc")) + .setMobile(params.get("to")) // 手机号 + .setReceiveTime(LocalDateTime.ofInstant(Instant.parse(params.get("updateTime")), ZoneId.of("UTC"))) // 状态报告时间 + .setSerialNo(params.get("smsMsgId")) // 发送序列号 + .setLogId(Long.valueOf(params.get("extend")))//logId + ); + + return respDTOS; } @Override @@ -173,56 +194,5 @@ public class HuaweiSmsClient extends AbstractSmsClient { //华为短信模板查询和发送短信,是不同的两套key和secret,与阿里、腾讯的区别较大,这里模板查询校验暂不实现。 return new SmsTemplateRespDTO().setId(null).setContent(null) .setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason(null); - } - - @Data - public static class SmsResponse { - - /** - * 是否成功 - */ - private boolean success; - - /** - * 厂商原返回体 - */ - private Object data; - - } - - - /** - * 短信接收状态 - * - * 参见 文档 - * - * @author scholar - */ - @Data - public static class SmsReceiveStatus { - - /** - * 本条状态报告对应的短信的接收方号码,仅当状态报告中携带了extend参数时才会同时携带该参数 - */ - @JsonProperty("to") - private String phoneNumber; - - /** - * 短信资源的更新时间,通常为短信平台接收短信状态报告的时间 - */ - @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT) - private LocalDateTime updateTime; - - /** - * 短信状态报告枚举值 - */ - private String status; - - /** - * 发送短信成功时返回的短信唯一标识。 - */ - private String smsMsgId; - } - -} +} \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClientTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClientTest.java new file mode 100644 index 000000000..e18a2b60d --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClientTest.java @@ -0,0 +1,114 @@ +package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; + +import cn.iocoder.yudao.framework.common.core.KeyValue; +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; +import com.google.common.collect.Lists; + +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.MockedStatic; + +import java.time.LocalDateTime; +import java.util.List; + +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mockStatic; + +/** + * {@link HuaweiSmsClient} 的单元测试 + * + * @author scholar + */ +public class HuaweiSmsClientTest extends BaseMockitoUnitTest { + + private final SmsChannelProperties properties = new SmsChannelProperties() + .setApiKey(randomString())// 随机一个 apiKey,避免构建报错 + .setApiSecret(randomString()) // 随机一个 apiSecret,避免构建报错 + .setSignature("芋道源码"); + + @InjectMocks + private HuaweiSmsClient smsClient = new HuaweiSmsClient(properties); + + @Test + public void testDoInit() { + // 调用 + smsClient.doInit(); + } + + @Test + public void testDoSendSms_success() throws Throwable { + + try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { + // 准备参数 + Long sendLogId = randomLongId(); + String mobile = randomString(); + String apiTemplateId = randomString() + " " + randomString(); + List> templateParams = Lists.newArrayList( + new KeyValue<>("1", 1234), new KeyValue<>("2", "login")); + + // mock 方法 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) + .thenReturn( + "{\"result\":[{\"originTo\":\"+86155****5678\",\"createTime\":\"2018-05-25T16:34:34Z\",\"from\":\"1069********0012\",\"smsMsgId\":\"d6e3cdd0-522b-4692-8304-a07553cdf591_8539659\",\"status\":\"000000\",\"countryId\":\"CN\",\"total\":2}],\"code\":\"000000\",\"description\":\"Success\"}\n" + ); + + // 调用 + SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, + apiTemplateId, templateParams); + // 断言 + assertTrue(result.getSuccess()); + assertEquals("d6e3cdd0-522b-4692-8304-a07553cdf591_8539659", result.getSerialNo()); + assertEquals("000000", result.getApiCode()); + + } + } + + @Test + public void testDoSendSms_fail() throws Throwable { + try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { + // 准备参数 + Long sendLogId = randomLongId(); + String mobile = randomString(); + String apiTemplateId = randomString() + " " + randomString(); + List> templateParams = Lists.newArrayList( + new KeyValue<>("1", 1234), new KeyValue<>("2", "login")); + + // mock 方法 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) + .thenReturn( + "{\"result\":[{\"originTo\":\"+86155****5678\",\"createTime\":\"2018-05-25T16:34:34Z\",\"from\":\"1069********0012\",\"smsMsgId\":\"d6e3cdd0-522b-4692-8304-a07553cdf591_8539659\",\"status\":\"E200015\",\"countryId\":\"CN\",\"total\":2}],\"code\":\"E000000\",\"description\":\"Success\"}\n" + ); + + // 调用 + SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, + apiTemplateId, templateParams); + // 断言 + assertFalse(result.getSuccess()); + assertEquals("d6e3cdd0-522b-4692-8304-a07553cdf591_8539659", result.getSerialNo()); + assertEquals("E200015", result.getApiCode()); + } + } + + @Test + public void testParseSmsReceiveStatus() { + // 准备参数 + String text = "sequence=1&total=1&statusDesc=%E7%94%A8%E6%88%B7%E5%B7%B2%E6%88%90%E5%8A%9F%E6%94%B6%E5%88%B0%E7%9F%AD%E4%BF%A1&updateTime=2024-08-15T03%3A00%3A34Z&source=2&smsMsgId=70207ed7-1d02-41b0-8537-bb25fd1c2364_143684459&status=DELIVRD&extend=176"; + + // 调用 + List statuses = smsClient.parseSmsReceiveStatus(text); + // 断言 + assertEquals(1, statuses.size()); + assertTrue(statuses.getFirst().getSuccess()); + assertEquals("DELIVRD", statuses.getFirst().getErrorCode()); + assertEquals(LocalDateTime.of(2024, 8, 15, 3, 0, 34), statuses.getFirst().getReceiveTime()); + assertEquals("70207ed7-1d02-41b0-8537-bb25fd1c2364_143684459", statuses.getFirst().getSerialNo()); + } + +} From 5b7e637ebf9c0419ffd0516043af33210c3ed0b3 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 18 Aug 2024 19:52:48 +0800 Subject: [PATCH 02/42] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E7=9F=AD=E4=BF=A1=EF=BC=9A=E5=8D=8E=E4=B8=BA?= =?UTF-8?q?=E4=BA=91=E7=9A=84=E5=AE=9E=E7=8E=B0=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/sms/SmsCallbackController.java | 3 - .../core/client/impl/AbstractSmsClient.java | 6 - .../sms/core/client/impl/AliyunSmsClient.java | 9 +- .../client/impl/DebugDingTalkSmsClient.java | 4 - .../sms/core/client/impl/HuaweiSmsClient.java | 211 +++++++----------- .../core/client/impl/TencentSmsClient.java | 4 - .../core/client/impl/HuaweiSmsClientTest.java | 55 +++-- .../sms/core/client/impl/SmsClientTests.java | 51 +---- 8 files changed, 130 insertions(+), 213 deletions(-) diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java index 622c4f95b..28581073f 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java @@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsChannelEnum; import cn.iocoder.yudao.module.system.service.sms.SmsSendService; -import com.xingyuv.captcha.util.StreamUtils; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.web.bind.annotation.*; @@ -13,8 +12,6 @@ import jakarta.annotation.Resource; import jakarta.annotation.security.PermitAll; import jakarta.servlet.http.HttpServletRequest; -import java.nio.charset.Charset; - import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @Tag(name = "管理后台 - 短信回调") diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AbstractSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AbstractSmsClient.java index 3b6e0eb0d..a1883bfdf 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AbstractSmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AbstractSmsClient.java @@ -26,15 +26,9 @@ public abstract class AbstractSmsClient implements SmsClient { * 初始化 */ public final void init() { - doInit(); log.debug("[init][配置({}) 初始化完成]", properties); } - /** - * 自定义初始化 - */ - protected abstract void doInit(); - public final void refresh(SmsChannelProperties properties) { // 判断是否更新 if (properties.equals(this.properties)) { diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java index f8158cdf2..558dbdef2 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java @@ -50,10 +50,6 @@ public class AliyunSmsClient extends AbstractSmsClient { Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); } - @Override - protected void doInit() { - } - @Override public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, List> templateParams) throws Throwable { @@ -80,7 +76,7 @@ public class AliyunSmsClient extends AbstractSmsClient { @Override public List parseSmsReceiveStatus(String text) { JSONArray statuses = JSONUtil.parseArray(text); - // 字段参考 + // 字段参考 https://help.aliyun.com/zh/sms/developer-reference/smsreport-2 return convertList(statuses, status -> { JSONObject statusObj = (JSONObject) status; return new SmsReceiveRespDTO() @@ -166,7 +162,8 @@ public class AliyunSmsClient extends AbstractSmsClient { String hashedRequestBody = DigestUtil.sha256Hex(requestBody); // 4. 构建 Authorization 签名 - String canonicalRequest = "POST" + "\n" + "/" + "\n" + queryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody; + String canonicalRequest = "POST" + "\n" + "/" + "\n" + queryString + "\n" + + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody; String hashedCanonicalRequest = DigestUtil.sha256Hex(canonicalRequest); String stringToSign = "ACS3-HMAC-SHA256" + "\n" + hashedCanonicalRequest; String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign); // 计算签名 diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/DebugDingTalkSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/DebugDingTalkSmsClient.java index e9fcc6c41..6d2f2d017 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/DebugDingTalkSmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/DebugDingTalkSmsClient.java @@ -36,10 +36,6 @@ public class DebugDingTalkSmsClient extends AbstractSmsClient { Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); } - @Override - protected void doInit() { - } - @Override public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, List> templateParams) throws Throwable { diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java index dfe5b2d45..4b073448b 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java @@ -1,13 +1,15 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.date.format.FastDateFormat; import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; - import cn.hutool.crypto.SecureUtil; +import cn.hutool.http.HttpUtil; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import cn.iocoder.yudao.framework.common.core.KeyValue; -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.http.HttpUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO; @@ -15,23 +17,19 @@ import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespD import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; - import lombok.extern.slf4j.Slf4j; import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; import java.net.URLEncoder; -import java.text.SimpleDateFormat; +import java.nio.charset.StandardCharsets; import java.time.Instant; +import java.time.LocalDateTime; import java.time.ZoneId; import java.util.*; - -import java.time.LocalDateTime; - import static cn.hutool.crypto.digest.DigestUtil.sha256Hex; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; -// todo @scholar:参考阿里云在优化下 /** * 华为短信客户端的实现类 * @@ -41,13 +39,11 @@ import static cn.hutool.crypto.digest.DigestUtil.sha256Hex; @Slf4j public class HuaweiSmsClient extends AbstractSmsClient { - public static final String URL = "https://smsapi.cn-north-4.myhuaweicloud.com:443/sms/batchSendSms/v1";//APP接入地址+接口访问URI - public static final String HOST = "smsapi.cn-north-4.myhuaweicloud.com:443"; - public static final String SIGNEDHEADERS = "content-type;host;x-sdk-date"; + private static final String URL = "https://smsapi.cn-north-4.myhuaweicloud.com:443/sms/batchSendSms/v1";//APP接入地址+接口访问URI + private static final String HOST = "smsapi.cn-north-4.myhuaweicloud.com:443"; + private static final String SIGNEDHEADERS = "content-type;host;x-sdk-date"; - @Override - protected void doInit() { - } + private static final String RESPONSE_CODE_SUCCESS = "000000"; public HuaweiSmsClient(SmsChannelProperties properties) { super(properties); @@ -58,139 +54,96 @@ public class HuaweiSmsClient extends AbstractSmsClient { @Override public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, List> templateParams) throws Throwable { + // 1. 执行请求 // 参考链接 https://support.huaweicloud.com/api-msgsms/sms_05_0001.html - // 相比较阿里短信,华为短信发送的时候需要额外的参数“通道号”,考虑到不破坏原有的的结构 - // 所以将 通道号 拼接到 apiTemplateId 字段中,格式为 "apiTemplateId 通道号"。空格为分隔符。 - String sender = StrUtil.subAfter(apiTemplateId, " ", true); //中国大陆短信签名通道号或全球短信通道号 - String templateId = StrUtil.subBefore(apiTemplateId, " ", true); //模板ID - - //选填,短信状态报告接收地址,推荐使用域名,为空或者不填表示不接收状态报告 + // 相比较阿里短信,华为短信发送的时候需要额外的参数“通道号”,考虑到不破坏原有的的结构, + // 所以将 sender 通道号,拼接到 apiTemplateId 字段中,格式为 "apiTemplateId 通道号"(空格为分隔符) + String sender = apiTemplateId.split(" ")[1]; // 中国大陆短信签名通道号或全球短信通道号 + String templateId = apiTemplateId.split(" ")[0]; //模板ID String statusCallBack = properties.getCallbackUrl(); + StringBuilder requestBody = new StringBuilder(); + appendToBody(requestBody, "from=", sender); + appendToBody(requestBody, "&to=", mobile); + appendToBody(requestBody, "&templateId=", templateId); + appendToBody(requestBody, "&templateParas=", JsonUtils.toJsonString( + convertList(templateParams, kv -> String.valueOf(kv.getValue())))); + appendToBody(requestBody, "&statusCallback=", statusCallBack); + appendToBody(requestBody, "&extend=", String.valueOf(sendLogId)); + JSONObject response = request("/sms/batchSendSms/v1/", "POST", requestBody.toString()); - List templateParas = CollectionUtils.convertList(templateParams, kv -> String.valueOf(kv.getValue())); - - JSONObject JsonResponse = request(sendLogId,sender,mobile,templateId,templateParas,statusCallBack); - - return new SmsSendRespDTO().setSuccess("000000".equals(JsonResponse.getStr("code"))) - .setSerialNo(JsonResponse.getJSONArray("result").getJSONObject(0).getStr("smsMsgId")) - .setApiCode(JsonResponse.getJSONArray("result").getJSONObject(0).getStr("status")); + // 2. 解析请求 + if (!response.containsKey("result")) { // 例如说:密钥不正确 + return new SmsSendRespDTO().setSuccess(false) + .setApiCode(response.getStr("code")) + .setApiMsg(response.getStr("description")); + } + JSONObject sendResult = response.getJSONArray("result").getJSONObject(0); + return new SmsSendRespDTO().setSuccess(RESPONSE_CODE_SUCCESS.equals(response.getStr("code"))) + .setSerialNo(sendResult.getStr("smsMsgId")).setApiCode(sendResult.getStr("status")); } - JSONObject request(Long sendLogId,String sender,String mobile,String templateId,List templateParas,String statusCallBack) throws UnsupportedEncodingException { - - SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.ENGLISH); - sdf.setTimeZone(TimeZone.getTimeZone("UTC")); - String sdkDate = sdf.format(new Date()); - - // ************* 步骤 1:拼接规范请求串 ************* - String httpRequestMethod = "POST"; - String canonicalUri = "/sms/batchSendSms/v1/"; - String canonicalQueryString = "";//查询参数为空 - String canonicalHeaders = "content-type:application/x-www-form-urlencoded\n" - + "host:"+ HOST +"\n" - + "x-sdk-date:" + sdkDate + "\n"; - String body = buildRequestBody(sender, mobile, templateId, templateParas, statusCallBack, sendLogId); - if (null == body || body.isEmpty()) { - return null; - } - String hashedRequestBody = sha256Hex(body); - String canonicalRequest = httpRequestMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n" - + canonicalHeaders + "\n" + SIGNEDHEADERS + "\n" + hashedRequestBody; - - // ************* 步骤 2:拼接待签名字符串 ************* - String hashedCanonicalRequest = sha256Hex(canonicalRequest); - String stringToSign = "SDK-HMAC-SHA256" + "\n" + sdkDate + "\n" + hashedCanonicalRequest; - - // ************* 步骤 3:计算签名 ************* - String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign); - - // ************* 步骤 4:拼接 Authorization ************* - String authorization = "SDK-HMAC-SHA256" + " " + "Access=" + properties.getApiKey() + ", " - + "SignedHeaders=" + SIGNEDHEADERS + ", " + "Signature=" + signature; - - // ************* 步骤 5:构造HttpRequest 并执行request请求,获得response ************* + /** + * 请求华为云短信 + * + * @see https://support.huaweicloud.com/api-msgsms/sms_05_0046.html + * @param uri 请求 URI + * @param method 请求 Method + * @param requestBody 请求 Body + * @return 请求结果 + */ + private JSONObject request(String uri, String method, String requestBody) { + // 1.1 请求 Header TreeMap headers = new TreeMap<>(); headers.put("Content-Type", "application/x-www-form-urlencoded"); + String sdkDate = FastDateFormat.getInstance("yyyyMMdd'T'HHmmss'Z'", TimeZone.getTimeZone("UTC")).format(new Date()); headers.put("X-Sdk-Date", sdkDate); headers.put("host", HOST); - headers.put("Authorization", authorization); - String responseBody = HttpUtils.post(URL, headers, body); + // 1.2 构建签名 Header + String canonicalQueryString = ""; // 查询参数为空 + String canonicalHeaders = "content-type:application/x-www-form-urlencoded\n" + + "host:"+ HOST +"\n" + "x-sdk-date:" + sdkDate + "\n"; + String canonicalRequest = method + "\n" + uri + "\n" + canonicalQueryString + "\n" + + canonicalHeaders + "\n" + SIGNEDHEADERS + "\n" + sha256Hex(requestBody); + String stringToSign = "SDK-HMAC-SHA256" + "\n" + sdkDate + "\n" + sha256Hex(canonicalRequest); + String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign); // 计算签名 + headers.put("Authorization", "SDK-HMAC-SHA256" + " " + "Access=" + properties.getApiKey() + + ", " + "SignedHeaders=" + SIGNEDHEADERS + ", " + "Signature=" + signature); + + // 2. 发起请求 + String responseBody = HttpUtils.post(URL, headers, requestBody); return JSONUtil.parseObj(responseBody); -// -// -// HttpResponse response = HttpRequest.post(URL) -// .header("Content-Type", "application/x-www-form-urlencoded") -// .header("X-Sdk-Date", sdkDate) -// .header("host",HOST) -// .header("Authorization", authorization) -// .body(body) -// .execute(); -// -// return JSONUtil.parseObj(response.body()); } - static String buildRequestBody(String sender, String receiver, String templateId, List templateParas, - String statusCallBack, Long sendLogId) throws UnsupportedEncodingException { - if (null == sender || null == receiver || null == templateId || sender.isEmpty() || receiver.isEmpty() - || templateId.isEmpty()) { - System.out.println("buildRequestBody(): sender, receiver or templateId is null."); - return null; - } - - StringBuilder body = new StringBuilder(); - appendToBody(body, "from=", sender); - appendToBody(body, "&to=", receiver); - appendToBody(body, "&templateId=", templateId); - appendToBody(body, "&templateParas=", JsonUtils.toJsonString(templateParas)); - appendToBody(body, "&statusCallback=", statusCallBack); - appendToBody(body, "&signature=", null); - appendToBody(body, "&extend=", String.valueOf(sendLogId)); - - return body.toString(); - } - - private static void appendToBody(StringBuilder body, String key, String val) throws UnsupportedEncodingException { - if (null != val && !val.isEmpty()) { - body.append(key).append(URLEncoder.encode(val, "UTF-8")); - } - } @Override public List parseSmsReceiveStatus(String requestBody) { - - System.out.println("text in parseSmsReceiveStatus===== " + requestBody); - - Map params = new HashMap<>(); - try { - String[] pairs = requestBody.split("&"); - for (String pair : pairs) { - int idx = pair.indexOf("="); - String key = URLDecoder.decode(pair.substring(0, idx), "UTF-8"); - String value = URLDecoder.decode(pair.substring(idx + 1), "UTF-8"); - params.put(key, value); - } - } catch (Exception e) { - e.printStackTrace(); - } - - List respDTOS = new ArrayList<>(); - respDTOS.add(new SmsReceiveRespDTO() - .setSuccess("DELIVRD".equals(params.get("status"))) // 是否接收成功 - .setErrorCode(params.get("status")) // 状态报告编码 - .setErrorMsg(params.get("statusDesc")) - .setMobile(params.get("to")) // 手机号 - .setReceiveTime(LocalDateTime.ofInstant(Instant.parse(params.get("updateTime")), ZoneId.of("UTC"))) // 状态报告时间 - .setSerialNo(params.get("smsMsgId")) // 发送序列号 - .setLogId(Long.valueOf(params.get("extend")))//logId - ); - - return respDTOS; + Map params = HttpUtil.decodeParamMap(requestBody, StandardCharsets.UTF_8); + // 字段参考 https://support.huaweicloud.com/api-msgsms/sms_05_0003.html + return ListUtil.of(new SmsReceiveRespDTO() + .setSuccess("DELIVRD".equals(params.get("status"))) // 是否接收成功 + .setErrorCode(params.get("status")) // 状态报告编码 + .setErrorMsg(params.get("statusDesc")) + .setMobile(params.get("to")) // 手机号 + .setReceiveTime(LocalDateTime.ofInstant(Instant.parse(params.get("updateTime")), ZoneId.of("UTC"))) // 状态报告时间 + .setSerialNo(params.get("smsMsgId")) // 发送序列号 + .setLogId(Long.valueOf(params.get("extend")))); // 用户序列号 } @Override public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable { - //华为短信模板查询和发送短信,是不同的两套key和secret,与阿里、腾讯的区别较大,这里模板查询校验暂不实现。 - return new SmsTemplateRespDTO().setId(null).setContent(null) + // 相比较阿里短信,华为短信发送的时候需要额外的参数“通道号”,考虑到不破坏原有的的结构, + // 所以将 sender 通道号,拼接到 apiTemplateId 字段中,格式为 "apiTemplateId 通道号"(空格为分隔符) + String[] strs = apiTemplateId.split(" "); + Assert.isTrue(strs.length == 2, "格式不正确,需要满足:apiTemplateId sender"); + return new SmsTemplateRespDTO().setId(strs[0]).setContent(null) .setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason(null); } + + @SuppressWarnings("CharsetObjectCanBeUsed") + private static void appendToBody(StringBuilder body, String key, String value) throws UnsupportedEncodingException { + if (StrUtil.isNotEmpty(value)) { + body.append(key).append(URLEncoder.encode(value, CharsetUtil.CHARSET_UTF_8.name())); + } + } + } \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java index 23a01db24..bd603b543 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java @@ -56,10 +56,6 @@ public class TencentSmsClient extends AbstractSmsClient { validateSdkAppId(properties); } - @Override - protected void doInit() { - } - /** * 参数校验腾讯云的 SDK AppId * diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClientTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClientTest.java index e18a2b60d..521c0ad18 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClientTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClientTest.java @@ -36,15 +36,8 @@ public class HuaweiSmsClientTest extends BaseMockitoUnitTest { @InjectMocks private HuaweiSmsClient smsClient = new HuaweiSmsClient(properties); - @Test - public void testDoInit() { - // 调用 - smsClient.doInit(); - } - @Test public void testDoSendSms_success() throws Throwable { - try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { // 准备参数 Long sendLogId = randomLongId(); @@ -55,9 +48,7 @@ public class HuaweiSmsClientTest extends BaseMockitoUnitTest { // mock 方法 httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) - .thenReturn( - "{\"result\":[{\"originTo\":\"+86155****5678\",\"createTime\":\"2018-05-25T16:34:34Z\",\"from\":\"1069********0012\",\"smsMsgId\":\"d6e3cdd0-522b-4692-8304-a07553cdf591_8539659\",\"status\":\"000000\",\"countryId\":\"CN\",\"total\":2}],\"code\":\"000000\",\"description\":\"Success\"}\n" - ); + .thenReturn("{\"result\":[{\"originTo\":\"+86155****5678\",\"createTime\":\"2018-05-25T16:34:34Z\",\"from\":\"1069********0012\",\"smsMsgId\":\"d6e3cdd0-522b-4692-8304-a07553cdf591_8539659\",\"status\":\"000000\",\"countryId\":\"CN\",\"total\":2}],\"code\":\"000000\",\"description\":\"Success\"}\n"); // 调用 SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, @@ -66,12 +57,11 @@ public class HuaweiSmsClientTest extends BaseMockitoUnitTest { assertTrue(result.getSuccess()); assertEquals("d6e3cdd0-522b-4692-8304-a07553cdf591_8539659", result.getSerialNo()); assertEquals("000000", result.getApiCode()); - } } @Test - public void testDoSendSms_fail() throws Throwable { + public void testDoSendSms_fail_01() throws Throwable { try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { // 准备参数 Long sendLogId = randomLongId(); @@ -82,17 +72,39 @@ public class HuaweiSmsClientTest extends BaseMockitoUnitTest { // mock 方法 httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) - .thenReturn( - "{\"result\":[{\"originTo\":\"+86155****5678\",\"createTime\":\"2018-05-25T16:34:34Z\",\"from\":\"1069********0012\",\"smsMsgId\":\"d6e3cdd0-522b-4692-8304-a07553cdf591_8539659\",\"status\":\"E200015\",\"countryId\":\"CN\",\"total\":2}],\"code\":\"E000000\",\"description\":\"Success\"}\n" - ); + .thenReturn("{\"result\":[{\"total\":1,\"originTo\":\"17321315478\",\"createTime\":\"2024-08-18T11:32:20Z\",\"from\":\"x8824060312575\",\"smsMsgId\":\"06e4b966-ad87-479f-8b74-f57fb7aafb60_304613461\",\"countryId\":\"CN\",\"status\":\"E200033\"}],\"code\":\"E000510\",\"description\":\"The SMS fails to be sent. For details, see status.\"}"); // 调用 SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams); // 断言 assertFalse(result.getSuccess()); - assertEquals("d6e3cdd0-522b-4692-8304-a07553cdf591_8539659", result.getSerialNo()); - assertEquals("E200015", result.getApiCode()); + assertEquals("06e4b966-ad87-479f-8b74-f57fb7aafb60_304613461", result.getSerialNo()); + assertEquals("E200033", result.getApiCode()); + } + } + + @Test + public void testDoSendSms_fail_02() throws Throwable { + try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { + // 准备参数 + Long sendLogId = randomLongId(); + String mobile = randomString(); + String apiTemplateId = randomString() + " " + randomString(); + List> templateParams = Lists.newArrayList( + new KeyValue<>("1", 1234), new KeyValue<>("2", "login")); + + // mock 方法 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) + .thenReturn("{\"code\":\"E000102\",\"description\":\"Invalid app_key.\"}"); + + // 调用 + SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, + apiTemplateId, templateParams); + // 断言 + assertFalse(result.getSuccess()); + assertEquals("E000102", result.getApiCode()); + assertEquals("Invalid app_key.", result.getApiMsg()); } } @@ -105,10 +117,11 @@ public class HuaweiSmsClientTest extends BaseMockitoUnitTest { List statuses = smsClient.parseSmsReceiveStatus(text); // 断言 assertEquals(1, statuses.size()); - assertTrue(statuses.getFirst().getSuccess()); - assertEquals("DELIVRD", statuses.getFirst().getErrorCode()); - assertEquals(LocalDateTime.of(2024, 8, 15, 3, 0, 34), statuses.getFirst().getReceiveTime()); - assertEquals("70207ed7-1d02-41b0-8537-bb25fd1c2364_143684459", statuses.getFirst().getSerialNo()); + SmsReceiveRespDTO status = statuses.get(0); + assertTrue(status.getSuccess()); + assertEquals("DELIVRD", status.getErrorCode()); + assertEquals(LocalDateTime.of(2024, 8, 15, 3, 0, 34), status.getReceiveTime()); + assertEquals("70207ed7-1d02-41b0-8537-bb25fd1c2364_143684459", status.getSerialNo()); } } diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java index 6eb22af1b..cb6c6c9f4 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java @@ -1,7 +1,7 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; import cn.iocoder.yudao.framework.common.core.KeyValue; -import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient; import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; @@ -11,7 +11,7 @@ import org.junit.jupiter.api.Test; import java.util.List; /** - * 各种 {@link SmsClientTests 集成测试 + * 各种 {@link SmsClient} 的集成测试 * * @author 芋道源码 */ @@ -23,8 +23,8 @@ public class SmsClientTests { @Disabled public void testAliyunSmsClient_getSmsTemplate() throws Throwable { SmsChannelProperties properties = new SmsChannelProperties() - .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR") - .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz"); + .setApiKey(System.getenv("SMS_ALIYUN_ACCESS_KEY")) + .setApiSecret(System.getenv("SMS_ALIYUN_SECRET_KEY")); AliyunSmsClient client = new AliyunSmsClient(properties); // 准备参数 String apiTemplateId = "SMS_207945135"; @@ -38,9 +38,9 @@ public class SmsClientTests { @Disabled public void testAliyunSmsClient_sendSms() throws Throwable { SmsChannelProperties properties = new SmsChannelProperties() - .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR") - .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz") - .setSignature("runpu"); + .setApiKey(System.getenv("SMS_ALIYUN_ACCESS_KEY")) + .setApiSecret(System.getenv("SMS_ALIYUN_SECRET_KEY")) + .setSignature("Ballcat"); AliyunSmsClient client = new AliyunSmsClient(properties); // 准备参数 Long sendLogId = System.currentTimeMillis(); @@ -52,35 +52,6 @@ public class SmsClientTests { System.out.println(sendRespDTO); } - @Test - @Disabled - public void testAliyunSmsClient_parseSmsReceiveStatus() { - SmsChannelProperties properties = new SmsChannelProperties() - .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR") - .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz"); - AliyunSmsClient client = new AliyunSmsClient(properties); - // 准备参数 - String text = "[\n" + - " {\n" + - " \"phone_number\" : \"13900000001\",\n" + - " \"send_time\" : \"2017-01-01 11:12:13\",\n" + - " \"report_time\" : \"2017-02-02 22:23:24\",\n" + - " \"success\" : true,\n" + - " \"err_code\" : \"DELIVERED\",\n" + - " \"err_msg\" : \"用户接收成功\",\n" + - " \"sms_size\" : \"1\",\n" + - " \"biz_id\" : \"12345\",\n" + - " \"out_id\" : \"67890\"\n" + - " }\n" + - "]"; - // mock 方法 - - // 调用 - List statuses = client.parseSmsReceiveStatus(text); - // 打印结果 - System.out.println(statuses); - } - // ========== 腾讯云 ========== @Test @@ -123,14 +94,14 @@ public class SmsClientTests { @Disabled public void testHuaweiSmsClient_sendSms() throws Throwable { SmsChannelProperties properties = new SmsChannelProperties() - .setApiKey("123") - .setApiSecret("456") + .setApiKey(System.getenv("SMS_HUAWEI_ACCESS_KEY")) + .setApiSecret(System.getenv("SMS_HUAWEI_SECRET_KEY")) .setSignature("runpu"); HuaweiSmsClient client = new HuaweiSmsClient(properties); // 准备参数 Long sendLogId = System.currentTimeMillis(); - String mobile = "15601691323"; - String apiTemplateId = "xx test01"; + String mobile = "17321315478"; + String apiTemplateId = "3644cdab863546a3b718d488659a99ef x8824060312575"; List> templateParams = List.of(new KeyValue<>("code", "1024")); // 调用 SmsSendRespDTO smsSendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, templateParams); From a5f82fedb3a47bd54f4f34f2c7d923195edd5d01 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 18 Aug 2024 20:54:51 +0800 Subject: [PATCH 03/42] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E7=9F=AD=E4=BF=A1=EF=BC=9A=E8=85=BE=E8=AE=AF?= =?UTF-8?q?=E4=BA=91=E7=9A=84=E5=AE=9E=E7=8E=B0=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/client/impl/TencentSmsClient.java | 106 +++++++----------- .../sms/core/client/impl/SmsClientTests.java | 14 ++- .../client/impl/TencentSmsClientTest.java | 27 ++++- 3 files changed, 75 insertions(+), 72 deletions(-) diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java index bd603b543..ae3138362 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java @@ -1,7 +1,11 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; +import cn.hutool.core.date.format.FastDateFormat; import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.HexUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.DigestUtil; +import cn.hutool.crypto.digest.HmacAlgorithm; import cn.hutool.json.JSONArray; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; @@ -14,12 +18,8 @@ import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateR import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; import com.google.common.annotations.VisibleForTesting; -import jakarta.xml.bind.DatatypeConverter; -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; import java.util.*; import static cn.hutool.crypto.digest.DigestUtil.sha256Hex; @@ -34,6 +34,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. */ public class TencentSmsClient extends AbstractSmsClient { + private static final String HOST = "sms.tencentcloudapi.com"; private static final String VERSION = "2021-01-11"; private static final String REGION = "ap-guangzhou"; @@ -89,7 +90,7 @@ public class TencentSmsClient extends AbstractSmsClient { body.put("PhoneNumberSet", new String[]{mobile}); body.put("SmsSdkAppId", getSdkAppId()); body.put("SignName", properties.getSignature()); - body.put("TemplateId",apiTemplateId); + body.put("TemplateId", apiTemplateId); body.put("TemplateParamSet", ArrayUtils.toArray(templateParams, param -> String.valueOf(param.getValue()))); JSONObject response = request("SendSms", body); @@ -102,11 +103,11 @@ public class TencentSmsClient extends AbstractSmsClient { .setApiCode(error.getStr("Code")) .setApiMsg(error.getStr("Message")); } - JSONObject responseData = responseResult.getJSONArray("SendStatusSet").getJSONObject(0); - return new SmsSendRespDTO().setSuccess(Objects.equals(API_CODE_SUCCESS, responseData.getStr("Code"))) + JSONObject sendResult = responseResult.getJSONArray("SendStatusSet").getJSONObject(0); + return new SmsSendRespDTO().setSuccess(Objects.equals(API_CODE_SUCCESS, sendResult.getStr("Code"))) .setApiRequestId(responseResult.getStr("RequestId")) - .setSerialNo(responseData.getStr("SerialNo")) - .setApiMsg(responseData.getStr("Message")); + .setSerialNo(sendResult.getStr("SerialNo")) + .setApiMsg(sendResult.getStr("Message")); } @Override @@ -133,14 +134,13 @@ public class TencentSmsClient extends AbstractSmsClient { body.put("TemplateIdSet", new Integer[]{Integer.valueOf(apiTemplateId)}); JSONObject response = request("DescribeSmsTemplateList", body); - // TODO @scholar:会有请求失败的情况么?类似发送的(那块逻辑我补充了) - JSONObject TemplateStatusSet = response.getJSONObject("Response").getJSONArray("DescribeTemplateStatusSet").getJSONObject(0); - String content = TemplateStatusSet.get("TemplateContent").toString(); - int templateStatus = Integer.parseInt(TemplateStatusSet.get("StatusCode").toString()); - String auditReason = TemplateStatusSet.get("ReviewReply").toString(); - - return new SmsTemplateRespDTO().setId(apiTemplateId).setContent(content) - .setAuditStatus(convertSmsTemplateAuditStatus(templateStatus)).setAuditReason(auditReason); + // 2. 解析请求 + JSONObject statusResult = response.getJSONObject("Response") + .getJSONArray("DescribeTemplateStatusSet").getJSONObject(0); + return new SmsTemplateRespDTO().setId(apiTemplateId) + .setContent(statusResult.get("TemplateContent").toString()) + .setAuditStatus(convertSmsTemplateAuditStatus(statusResult.getInt("StatusCode"))) + .setAuditReason(statusResult.get("ReviewReply").toString()); } @VisibleForTesting @@ -163,63 +163,39 @@ public class TencentSmsClient extends AbstractSmsClient { * @return 请求结果 */ private JSONObject request(String action, TreeMap body) throws Exception { - String timestamp = String.valueOf(System.currentTimeMillis() / 1000); - // TODO @scholar:这个 format,看看怎么写的可以简化点 - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); - // 注意时区,否则容易出错 - sdf.setTimeZone(TimeZone.getTimeZone("UTC")); - String date = sdf.format(new Date(Long.valueOf(timestamp + "000"))); - - // TODO @scholar:这个步骤,看看怎么参考阿里云 client,归类下;1. 2.1 2.2 这种 - // ************* 步骤 1:拼接规范请求串 ************* - // TODO @scholar:这个 hsot 枚举下; - String host = "sms.tencentcloudapi.com"; //APP接入地址+接口访问URI - String httpMethod = "POST"; // 请求方式 - String canonicalUri = "/"; - String canonicalQueryString = ""; - - String canonicalHeaders = "content-type:application/json; charset=utf-8\n" - + "host:" + host + "\n" + "x-tc-action:" + action.toLowerCase() + "\n"; - String signedHeaders = "content-type;host;x-tc-action"; - String hashedRequestBody = sha256Hex(JSONUtil.toJsonStr(body)); - // TODO @scholar:换行下,不然单行太长了 - String canonicalRequest = httpMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody; - - // ************* 步骤 2:拼接待签名字符串 ************* - String credentialScope = date + "/" + "sms" + "/" + "tc3_request"; - String hashedCanonicalRequest = sha256Hex(canonicalRequest); - String stringToSign = "TC3-HMAC-SHA256" + "\n" + timestamp + "\n" + credentialScope + "\n" + hashedCanonicalRequest; - - // ************* 步骤 3:计算签名 ************* - byte[] secretDate = hmac256(("TC3" + properties.getApiSecret()).getBytes(StandardCharsets.UTF_8), date); - byte[] secretService = hmac256(secretDate, "sms"); - byte[] secretSigning = hmac256(secretService, "tc3_request"); - String signature = DatatypeConverter.printHexBinary(hmac256(secretSigning, stringToSign)).toLowerCase(); - - // ************* 步骤 4:拼接 Authorization ************* - String authorization = "TC3-HMAC-SHA256" + " " + "Credential=" + getApiKey() + "/" + credentialScope + ", " - + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature; - - // ************* 步骤 5:构造HttpRequest 并执行request请求,获得response ************* + // 1.1 请求 Header Map headers = new HashMap<>(); - headers.put("Authorization", authorization); headers.put("Content-Type", "application/json; charset=utf-8"); - headers.put("Host", host); + headers.put("Host", HOST); headers.put("X-TC-Action", action); - headers.put("X-TC-Timestamp", timestamp); + Date now = new Date(); + String nowStr = FastDateFormat.getInstance("yyyy-MM-dd", TimeZone.getTimeZone("UTC")).format(now); + headers.put("X-TC-Timestamp", String.valueOf(now.getTime() / 1000)); headers.put("X-TC-Version", VERSION); headers.put("X-TC-Region", REGION); - String responseBody = HttpUtils.post("https://" + host, headers, JSONUtil.toJsonStr(body)); + // 1.2 构建签名 Header + String canonicalQueryString = ""; + String canonicalHeaders = "content-type:application/json; charset=utf-8\n" + + "host:" + HOST + "\n" + "x-tc-action:" + action.toLowerCase() + "\n"; + String signedHeaders = "content-type;host;x-tc-action"; + String canonicalRequest = "POST" + "\n" + "/" + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + + signedHeaders + "\n" + sha256Hex(JSONUtil.toJsonStr(body)); + String credentialScope = nowStr + "/" + "sms" + "/" + "tc3_request"; + String stringToSign = "TC3-HMAC-SHA256" + "\n" + now.getTime() / 1000 + "\n" + credentialScope + "\n" + + sha256Hex(canonicalRequest); + byte[] secretService = hmac256(hmac256(("TC3" + properties.getApiSecret()).getBytes(StandardCharsets.UTF_8), nowStr), "sms"); + String signature = HexUtil.encodeHexStr(hmac256(hmac256(secretService, "tc3_request"), stringToSign)); + headers.put("Authorization", "TC3-HMAC-SHA256" + " " + "Credential=" + getApiKey() + "/" + credentialScope + ", " + + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature); + // 2. 发起请求 + String responseBody = HttpUtils.post("https://" + HOST, headers, JSONUtil.toJsonStr(body)); return JSONUtil.parseObj(responseBody); } - // TODO @scholar:使用 hutool 简化下 - private static byte[] hmac256(byte[] key, String msg) throws Exception { - Mac mac = Mac.getInstance("HmacSHA256"); - SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm()); - mac.init(secretKeySpec); - return mac.doFinal(msg.getBytes(StandardCharsets.UTF_8)); + private static byte[] hmac256(byte[] key, String msg) { + return DigestUtil.hmac(HmacAlgorithm.HmacSHA256, key).digest(msg); } + } \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java index cb6c6c9f4..3105c4369 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java @@ -57,15 +57,16 @@ public class SmsClientTests { @Test @Disabled public void testTencentSmsClient_sendSms() throws Throwable { + String sdkAppId = "1400500458"; SmsChannelProperties properties = new SmsChannelProperties() - .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR 1428926523") - .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz") + .setApiKey(System.getenv("SMS_TENCENT_ACCESS_KEY") + " " + sdkAppId) + .setApiSecret(System.getenv("SMS_TENCENT_SECRET_KEY")) .setSignature("芋道源码"); TencentSmsClient client = new TencentSmsClient(properties); // 准备参数 Long sendLogId = System.currentTimeMillis(); String mobile = "15601691323"; - String apiTemplateId = "2136358"; + String apiTemplateId = "358212"; // 调用 SmsSendRespDTO sendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, List.of(new KeyValue<>("code", "1024"))); // 打印结果 @@ -75,13 +76,14 @@ public class SmsClientTests { @Test @Disabled public void testTencentSmsClient_getSmsTemplate() throws Throwable { + String sdkAppId = "1400500458"; SmsChannelProperties properties = new SmsChannelProperties() - .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR 1428926523") - .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz") + .setApiKey(System.getenv("SMS_TENCENT_ACCESS_KEY") + " " + sdkAppId) + .setApiSecret(System.getenv("SMS_TENCENT_SECRET_KEY")) .setSignature("芋道源码"); TencentSmsClient client = new TencentSmsClient(properties); // 准备参数 - String apiTemplateId = "2136358"; + String apiTemplateId = "358212"; // 调用 SmsTemplateRespDTO template = client.getSmsTemplate(apiTemplateId); // 打印结果 diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClientTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClientTest.java index b25540b44..060a34558 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClientTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClientTest.java @@ -78,7 +78,7 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest { } @Test - public void testDoSendSms_fail() throws Throwable { + public void testDoSendSms_fail_01() throws Throwable { try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { // 准备参数 Long sendLogId = randomLongId(); @@ -117,6 +117,31 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest { } } + @Test + public void testDoSendSms_fail_02() throws Throwable { + try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { + // 准备参数 + Long sendLogId = randomLongId(); + String mobile = randomString(); + String apiTemplateId = randomString(); + List> templateParams = Lists.newArrayList( + new KeyValue<>("1", 1234), new KeyValue<>("2", "login")); + + // mock 方法 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) + .thenReturn("{\"Response\":{\"Error\":{\"Code\":\"AuthFailure.SecretIdNotFound\",\"Message\":\"The SecretId is not found, please ensure that your SecretId is correct.\"},\"RequestId\":\"2a88f82a-261c-4ac6-9fa9-c7d01aaa486a\"}}"); + + // 调用 + SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, + apiTemplateId, templateParams); + // 断言 + assertFalse(result.getSuccess()); + assertEquals("2a88f82a-261c-4ac6-9fa9-c7d01aaa486a", result.getApiRequestId()); + assertEquals("AuthFailure.SecretIdNotFound", result.getApiCode()); + assertEquals("The SecretId is not found, please ensure that your SecretId is correct.", result.getApiMsg()); + } + } + @Test public void testParseSmsReceiveStatus() { // 准备参数 From a685402688004f989886018942b80deaa4a50526 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 18 Aug 2024 21:04:15 +0800 Subject: [PATCH 04/42] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E7=9F=AD=E4=BF=A1=EF=BC=9A=E7=AE=80=E5=8C=96?= =?UTF-8?q?=E7=9F=AD=E4=BF=A1=20channel=20=E7=9A=84=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sms/core/client/SmsClientFactory.java | 3 +- .../client/impl/SmsClientFactoryImpl.java | 3 +- .../service/sms/SmsChannelServiceImpl.java | 83 +++---------------- .../service/sms/SmsChannelServiceTest.java | 29 ++----- 4 files changed, 19 insertions(+), 99 deletions(-) diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/SmsClientFactory.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/SmsClientFactory.java index a1133177f..ad878b78e 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/SmsClientFactory.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/SmsClientFactory.java @@ -30,7 +30,8 @@ public interface SmsClientFactory { * 创建短信 Client * * @param properties 配置对象 + * @return 短信 Client */ - void createOrUpdateSmsClient(SmsChannelProperties properties); + SmsClient createOrUpdateSmsClient(SmsChannelProperties properties); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientFactoryImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientFactoryImpl.java index 326cad058..dde1475d4 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientFactoryImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientFactoryImpl.java @@ -59,7 +59,7 @@ public class SmsClientFactoryImpl implements SmsClientFactory { } @Override - public void createOrUpdateSmsClient(SmsChannelProperties properties) { + public SmsClient createOrUpdateSmsClient(SmsChannelProperties properties) { AbstractSmsClient client = channelIdClients.get(properties.getId()); if (client == null) { client = this.createSmsClient(properties); @@ -68,6 +68,7 @@ public class SmsClientFactoryImpl implements SmsClientFactory { } else { client.refresh(properties); } + return client; } private AbstractSmsClient createSmsClient(SmsChannelProperties properties) { diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceImpl.java index cca3741fe..5c6f36d8f 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceImpl.java @@ -1,27 +1,21 @@ package cn.iocoder.yudao.module.system.service.sms; -import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient; -import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClientFactory; -import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO; import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelSaveReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO; import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import lombok.Getter; +import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient; +import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClientFactory; +import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import jakarta.annotation.Resource; -import java.time.Duration; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_HAS_CHILDREN; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS; @@ -34,46 +28,6 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNE @Slf4j public class SmsChannelServiceImpl implements SmsChannelService { - /** - * {@link SmsClient} 缓存,通过它异步刷新 smsClientFactory - */ - @Getter - private final LoadingCache idClientCache = buildAsyncReloadingCache(Duration.ofSeconds(10L), - new CacheLoader() { - - @Override - public SmsClient load(Long id) { - // 查询,然后尝试刷新 - SmsChannelDO channel = smsChannelMapper.selectById(id); - if (channel != null) { - SmsChannelProperties properties = BeanUtils.toBean(channel, SmsChannelProperties.class); - smsClientFactory.createOrUpdateSmsClient(properties); - } - return smsClientFactory.getSmsClient(id); - } - - }); - - /** - * {@link SmsClient} 缓存,通过它异步刷新 smsClientFactory - */ - @Getter - private final LoadingCache codeClientCache = buildAsyncReloadingCache(Duration.ofSeconds(60L), - new CacheLoader() { - - @Override - public SmsClient load(String code) { - // 查询,然后尝试刷新 - SmsChannelDO channel = smsChannelMapper.selectByCode(code); - if (channel != null) { - SmsChannelProperties properties = BeanUtils.toBean(channel, SmsChannelProperties.class); - smsClientFactory.createOrUpdateSmsClient(properties); - } - return smsClientFactory.getSmsClient(code); - } - - }); - @Resource private SmsClientFactory smsClientFactory; @@ -93,41 +47,22 @@ public class SmsChannelServiceImpl implements SmsChannelService { @Override public void updateSmsChannel(SmsChannelSaveReqVO updateReqVO) { // 校验存在 - SmsChannelDO channel = validateSmsChannelExists(updateReqVO.getId()); + validateSmsChannelExists(updateReqVO.getId()); // 更新 SmsChannelDO updateObj = BeanUtils.toBean(updateReqVO, SmsChannelDO.class); smsChannelMapper.updateById(updateObj); - - // 清空缓存 - clearCache(updateReqVO.getId(), channel.getCode()); } @Override public void deleteSmsChannel(Long id) { // 校验存在 - SmsChannelDO channel = validateSmsChannelExists(id); + validateSmsChannelExists(id); // 校验是否有在使用该账号的模版 if (smsTemplateService.getSmsTemplateCountByChannelId(id) > 0) { throw exception(SMS_CHANNEL_HAS_CHILDREN); } // 删除 smsChannelMapper.deleteById(id); - - // 清空缓存 - clearCache(id, channel.getCode()); - } - - /** - * 清空指定渠道编号的缓存 - * - * @param id 渠道编号 - * @param code 渠道编码 - */ - private void clearCache(Long id, String code) { - idClientCache.invalidate(id); - if (StrUtil.isNotEmpty(code)) { - codeClientCache.invalidate(code); - } } private SmsChannelDO validateSmsChannelExists(Long id) { @@ -155,12 +90,14 @@ public class SmsChannelServiceImpl implements SmsChannelService { @Override public SmsClient getSmsClient(Long id) { - return idClientCache.getUnchecked(id); + SmsChannelDO channel = smsChannelMapper.selectById(id); + SmsChannelProperties properties = BeanUtils.toBean(channel, SmsChannelProperties.class); + return smsClientFactory.createOrUpdateSmsClient(properties); } @Override public SmsClient getSmsClient(String code) { - return codeClientCache.getUnchecked(code); + return smsClientFactory.getSmsClient(code); } } diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceTest.java index 1cc9152c3..295911a17 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceTest.java @@ -57,9 +57,6 @@ public class SmsChannelServiceTest extends BaseDbUnitTest { // 校验记录的属性是否正确 SmsChannelDO smsChannel = smsChannelMapper.selectById(smsChannelId); assertPojoEquals(reqVO, smsChannel, "id"); - // 断言 cache - assertNull(smsChannelService.getIdClientCache().getIfPresent(smsChannel.getId())); - assertNull(smsChannelService.getCodeClientCache().getIfPresent(smsChannel.getCode())); } @Test @@ -79,9 +76,6 @@ public class SmsChannelServiceTest extends BaseDbUnitTest { // 校验是否更新正确 SmsChannelDO smsChannel = smsChannelMapper.selectById(reqVO.getId()); // 获取最新的 assertPojoEquals(reqVO, smsChannel); - // 断言 cache - assertNull(smsChannelService.getIdClientCache().getIfPresent(smsChannel.getId())); - assertNull(smsChannelService.getCodeClientCache().getIfPresent(smsChannel.getCode())); } @Test @@ -105,9 +99,6 @@ public class SmsChannelServiceTest extends BaseDbUnitTest { smsChannelService.deleteSmsChannel(id); // 校验数据不存在了 assertNull(smsChannelMapper.selectById(id)); - // 断言 cache - assertNull(smsChannelService.getIdClientCache().getIfPresent(dbSmsChannel.getId())); - assertNull(smsChannelService.getCodeClientCache().getIfPresent(dbSmsChannel.getCode())); } @Test @@ -196,29 +187,23 @@ public class SmsChannelServiceTest extends BaseDbUnitTest { // mock 数据 SmsChannelDO channel = randomPojo(SmsChannelDO.class); smsChannelMapper.insert(channel); - // mock 参数 + // 准备参数 Long id = channel.getId(); // mock 方法 SmsClient mockClient = mock(SmsClient.class); - when(smsClientFactory.getSmsClient(eq(id))).thenReturn(mockClient); + SmsChannelProperties properties = BeanUtils.toBean(channel, SmsChannelProperties.class); + when(smsClientFactory.createOrUpdateSmsClient(eq(properties))).thenReturn(mockClient); // 调用 SmsClient client = smsChannelService.getSmsClient(id); // 断言 assertSame(client, mockClient); - verify(smsClientFactory).createOrUpdateSmsClient(argThat(arg -> { - SmsChannelProperties properties = BeanUtils.toBean(channel, SmsChannelProperties.class); - return properties.equals(arg); - })); } @Test public void testGetSmsClient_code() { - // mock 数据 - SmsChannelDO channel = randomPojo(SmsChannelDO.class); - smsChannelMapper.insert(channel); - // mock 参数 - String code = channel.getCode(); + // 准备参数 + String code = randomString(); // mock 方法 SmsClient mockClient = mock(SmsClient.class); when(smsClientFactory.getSmsClient(eq(code))).thenReturn(mockClient); @@ -227,10 +212,6 @@ public class SmsChannelServiceTest extends BaseDbUnitTest { SmsClient client = smsChannelService.getSmsClient(code); // 断言 assertSame(client, mockClient); - verify(smsClientFactory).createOrUpdateSmsClient(argThat(arg -> { - SmsChannelProperties properties = BeanUtils.toBean(channel, SmsChannelProperties.class); - return properties.equals(arg); - })); } } From c7ccb8286ac01238a28334bcfa966df357c4c190 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Mon, 19 Aug 2024 11:32:53 +0800 Subject: [PATCH 05/42] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91MALL:=20=E5=94=AE=E5=90=8E=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20userId=20=E6=A3=80=E7=B4=A2=E6=9D=A1?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/admin/aftersale/vo/AfterSalePageReqVO.java | 3 +++ .../module/trade/dal/mysql/aftersale/AfterSaleMapper.java | 1 + 2 files changed, 4 insertions(+) diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/AfterSalePageReqVO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/AfterSalePageReqVO.java index f74c84b8f..f4b67fa5a 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/AfterSalePageReqVO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/AfterSalePageReqVO.java @@ -24,6 +24,9 @@ public class AfterSalePageReqVO extends PageParam { @Schema(description = "售后流水号", example = "202211190847450020500077") private String no; + @Schema(description = "用户编号", example = "1024") + private Long userId; + @Schema(description = "售后状态", example = "10") @InEnum(value = AfterSaleStatusEnum.class, message = "售后状态必须是 {value}") private Integer status; diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleMapper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleMapper.java index 68a09a82a..b5e507a91 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleMapper.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleMapper.java @@ -17,6 +17,7 @@ public interface AfterSaleMapper extends BaseMapperX { default PageResult selectPage(AfterSalePageReqVO reqVO) { return selectPage(reqVO, new LambdaQueryWrapperX() .likeIfPresent(AfterSaleDO::getNo, reqVO.getNo()) + .eqIfPresent(AfterSaleDO::getUserId, reqVO.getUserId()) .eqIfPresent(AfterSaleDO::getStatus, reqVO.getStatus()) .eqIfPresent(AfterSaleDO::getType, reqVO.getType()) .eqIfPresent(AfterSaleDO::getWay, reqVO.getWay()) From 2d29bf4e6f7486f1f2b4d359d6796d92e996ad54 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 19 Aug 2024 12:28:31 +0800 Subject: [PATCH 06/42] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E7=9F=AD=E4=BF=A1=EF=BC=9A=E5=8D=8E=E4=B8=BA?= =?UTF-8?q?=E4=BA=91=E7=9A=84=E5=AE=9E=E7=8E=B0=E4=BC=98=E5=8C=96=EF=BC=8C?= =?UTF-8?q?=E8=B0=83=E6=95=B4=20sender=20=E9=85=8D=E7=BD=AE=E5=88=B0=20acc?= =?UTF-8?q?essKey=20=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sms/core/client/impl/HuaweiSmsClient.java | 45 +++++++++++++------ .../core/client/impl/HuaweiSmsClientTest.java | 2 +- .../sms/core/client/impl/SmsClientTests.java | 5 ++- 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java index 4b073448b..82f55395e 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java @@ -49,25 +49,43 @@ public class HuaweiSmsClient extends AbstractSmsClient { super(properties); Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空"); Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); + validateSender(properties); + } + + /** + * 参数校验华为云的 sender 通道号 + * + * 原因是:验华为云发放短信的时候,需要额外的参数 sender + * + * 解决方案:考虑到不破坏原有的 apiKey + apiSecret 的结构,所以将 secretId 拼接到 apiKey 字段中,格式为 "secretId sdkAppId"。 + * + * @param properties 配置 + */ + private static void validateSender(SmsChannelProperties properties) { + String combineKey = properties.getApiKey(); + Assert.notEmpty(combineKey, "apiKey 不能为空"); + String[] keys = combineKey.trim().split(" "); + Assert.isTrue(keys.length == 2, "华为云短信 apiKey 配置格式错误,请配置 为[accessKeyId sender]"); + } + + private String getAccessKey() { + return StrUtil.subBefore(properties.getApiKey(), " ", true); + } + + private String getSender() { + return StrUtil.subAfter(properties.getApiKey(), " ", true); } @Override public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, List> templateParams) throws Throwable { - // 1. 执行请求 - // 参考链接 https://support.huaweicloud.com/api-msgsms/sms_05_0001.html - // 相比较阿里短信,华为短信发送的时候需要额外的参数“通道号”,考虑到不破坏原有的的结构, - // 所以将 sender 通道号,拼接到 apiTemplateId 字段中,格式为 "apiTemplateId 通道号"(空格为分隔符) - String sender = apiTemplateId.split(" ")[1]; // 中国大陆短信签名通道号或全球短信通道号 - String templateId = apiTemplateId.split(" ")[0]; //模板ID - String statusCallBack = properties.getCallbackUrl(); StringBuilder requestBody = new StringBuilder(); - appendToBody(requestBody, "from=", sender); + appendToBody(requestBody, "from=", getSender()); appendToBody(requestBody, "&to=", mobile); - appendToBody(requestBody, "&templateId=", templateId); + appendToBody(requestBody, "&templateId=", apiTemplateId); appendToBody(requestBody, "&templateParas=", JsonUtils.toJsonString( convertList(templateParams, kv -> String.valueOf(kv.getValue())))); - appendToBody(requestBody, "&statusCallback=", statusCallBack); + appendToBody(requestBody, "&statusCallback=", properties.getCallbackUrl()); appendToBody(requestBody, "&extend=", String.valueOf(sendLogId)); JSONObject response = request("/sms/batchSendSms/v1/", "POST", requestBody.toString()); @@ -107,7 +125,7 @@ public class HuaweiSmsClient extends AbstractSmsClient { + canonicalHeaders + "\n" + SIGNEDHEADERS + "\n" + sha256Hex(requestBody); String stringToSign = "SDK-HMAC-SHA256" + "\n" + sdkDate + "\n" + sha256Hex(canonicalRequest); String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign); // 计算签名 - headers.put("Authorization", "SDK-HMAC-SHA256" + " " + "Access=" + properties.getApiKey() + headers.put("Authorization", "SDK-HMAC-SHA256" + " " + "Access=" + getAccessKey() + ", " + "SignedHeaders=" + SIGNEDHEADERS + ", " + "Signature=" + signature); // 2. 发起请求 @@ -131,11 +149,10 @@ public class HuaweiSmsClient extends AbstractSmsClient { @Override public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable { - // 相比较阿里短信,华为短信发送的时候需要额外的参数“通道号”,考虑到不破坏原有的的结构, - // 所以将 sender 通道号,拼接到 apiTemplateId 字段中,格式为 "apiTemplateId 通道号"(空格为分隔符) + // 华为短信模板查询和发送短信,是不同的两套 key 和 secret,与阿里、腾讯的区别较大,这里模板查询校验暂不实现 String[] strs = apiTemplateId.split(" "); Assert.isTrue(strs.length == 2, "格式不正确,需要满足:apiTemplateId sender"); - return new SmsTemplateRespDTO().setId(strs[0]).setContent(null) + return new SmsTemplateRespDTO().setId(apiTemplateId).setContent(null) .setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason(null); } diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClientTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClientTest.java index 521c0ad18..3f97412c8 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClientTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClientTest.java @@ -29,7 +29,7 @@ import static org.mockito.Mockito.mockStatic; public class HuaweiSmsClientTest extends BaseMockitoUnitTest { private final SmsChannelProperties properties = new SmsChannelProperties() - .setApiKey(randomString())// 随机一个 apiKey,避免构建报错 + .setApiKey(randomString() + " " + randomString()) // 随机一个 apiKey,避免构建报错 .setApiSecret(randomString()) // 随机一个 apiSecret,避免构建报错 .setSignature("芋道源码"); diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java index 3105c4369..bc0dcf980 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java @@ -95,15 +95,16 @@ public class SmsClientTests { @Test @Disabled public void testHuaweiSmsClient_sendSms() throws Throwable { + String sender = "x8824060312575"; SmsChannelProperties properties = new SmsChannelProperties() - .setApiKey(System.getenv("SMS_HUAWEI_ACCESS_KEY")) + .setApiKey(System.getenv("SMS_HUAWEI_ACCESS_KEY") + " " + sender) .setApiSecret(System.getenv("SMS_HUAWEI_SECRET_KEY")) .setSignature("runpu"); HuaweiSmsClient client = new HuaweiSmsClient(properties); // 准备参数 Long sendLogId = System.currentTimeMillis(); String mobile = "17321315478"; - String apiTemplateId = "3644cdab863546a3b718d488659a99ef x8824060312575"; + String apiTemplateId = "3644cdab863546a3b718d488659a99ef"; List> templateParams = List.of(new KeyValue<>("code", "1024")); // 调用 SmsSendRespDTO smsSendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, templateParams); From 11a6e8ebf75f1152df38756cc4bceae7620c38f4 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Mon, 19 Aug 2024 17:22:13 +0800 Subject: [PATCH 07/42] =?UTF-8?q?=E3=80=90=E6=96=B0=E5=A2=9E=E3=80=91MALL:?= =?UTF-8?q?=20=E6=9B=B4=E6=96=B0=E4=BC=9A=E5=91=98=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E4=BD=99=E9=A2=9D=E7=9B=B8=E5=85=B3=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao-module-member-biz/pom.xml | 5 +++ .../admin/user/MemberUserController.java | 14 ++++-- .../user/vo/MemberUserUpdateBalanceReqVO.java | 21 +++++++++ .../module/pay/api/wallet/PayWalletApi.java | 19 ++++++++ .../dto/PayWalletUpdateBalanceReqDTO.java | 23 ++++++++++ .../enums/wallet/PayWalletBizTypeEnum.java | 3 +- .../pay/api/wallet/PayWalletApiImpl.java | 43 +++++++++++++++++++ .../service/wallet/PayWalletServiceImpl.java | 5 ++- 8 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserUpdateBalanceReqVO.java create mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java create mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletUpdateBalanceReqDTO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java diff --git a/yudao-module-member/yudao-module-member-biz/pom.xml b/yudao-module-member/yudao-module-member-biz/pom.xml index 3c9b81e65..368a3cba7 100644 --- a/yudao-module-member/yudao-module-member-biz/pom.xml +++ b/yudao-module-member/yudao-module-member-biz/pom.xml @@ -33,6 +33,11 @@ yudao-module-infra-api ${revision} + + cn.iocoder.boot + yudao-module-pay-api + ${revision} + diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/MemberUserController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/MemberUserController.java index c9c6c06e0..09978f6e0 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/MemberUserController.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/MemberUserController.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.member.controller.admin.user; import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.member.controller.admin.user.vo.*; import cn.iocoder.yudao.module.member.convert.user.MemberUserConvert; import cn.iocoder.yudao.module.member.dal.dataobject.group.MemberGroupDO; @@ -15,15 +16,17 @@ import cn.iocoder.yudao.module.member.service.level.MemberLevelService; import cn.iocoder.yudao.module.member.service.point.MemberPointRecordService; import cn.iocoder.yudao.module.member.service.tag.MemberTagService; import cn.iocoder.yudao.module.member.service.user.MemberUserService; +import cn.iocoder.yudao.module.pay.api.wallet.PayWalletApi; +import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletUpdateBalanceReqDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import jakarta.annotation.Resource; -import jakarta.validation.Valid; import java.util.Collection; import java.util.List; import java.util.Objects; @@ -50,6 +53,8 @@ public class MemberUserController { private MemberGroupService memberGroupService; @Resource private MemberPointRecordService memberPointRecordService; + @Resource + private PayWalletApi payWalletApi; @PutMapping("/update") @Operation(summary = "更新会员用户") @@ -79,8 +84,9 @@ public class MemberUserController { @PutMapping("/update-balance") @Operation(summary = "更新会员用户余额") @PreAuthorize("@ss.hasPermission('member:user:update-balance')") - public CommonResult updateUserBalance(@Valid @RequestBody Long id) { - // todo @jason:增加一个【修改余额】 + public CommonResult updateUserBalance(@Valid @RequestBody MemberUserUpdateBalanceReqVO updateReqVO) { + payWalletApi.updateBalance(BeanUtils.toBean(updateReqVO, PayWalletUpdateBalanceReqDTO.class) + .setUserId(updateReqVO.getId())); return success(true); } diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserUpdateBalanceReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserUpdateBalanceReqVO.java new file mode 100644 index 000000000..fe694df67 --- /dev/null +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserUpdateBalanceReqVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.member.controller.admin.user.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.ToString; + +@Schema(description = "管理后台 - 用户修改余额 Request VO") +@Data +@ToString(callSuper = true) +public class MemberUserUpdateBalanceReqVO { + + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23788") + @NotNull(message = "用户编号不能为空") + private Long id; + + @Schema(description = "变动余额,正数为增加,负数为减少", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") + @NotNull(message = "变动余额不能为空") + private Integer balance; + +} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java new file mode 100644 index 000000000..a99bafe93 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.pay.api.wallet; + +import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletUpdateBalanceReqDTO; + +/** + * 会员钱包 API 接口 + * + * @author HUIHUI + */ +public interface PayWalletApi { + + /** + * 更新钱包余额 + * + * @param reqDTO 请求 + */ + void updateBalance(PayWalletUpdateBalanceReqDTO reqDTO); + +} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletUpdateBalanceReqDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletUpdateBalanceReqDTO.java new file mode 100644 index 000000000..02a7756dd --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletUpdateBalanceReqDTO.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.module.pay.api.wallet.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 钱包余额更新 Request DTO + * + * @author HUIHUI + */ +@Data +public class PayWalletUpdateBalanceReqDTO { + + @NotNull(message = "用户编号不能为空") + private Long userId; + + /** + * 变动余额,正数为增加,负数为减少 + */ + @NotNull(message = "变动余额不能为空") + private Integer balance; + +} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java index 20e0a8b09..7892db543 100644 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java @@ -18,7 +18,8 @@ public enum PayWalletBizTypeEnum implements IntArrayValuable { RECHARGE(1, "充值"), RECHARGE_REFUND(2, "充值退款"), PAYMENT(3, "支付"), - PAYMENT_REFUND(4, "支付退款"); + PAYMENT_REFUND(4, "支付退款"), + UPDATE_BALANCE(5, "更新余额"); // TODO 后续增加 diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java new file mode 100644 index 000000000..9f94e1a8f --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.module.pay.api.wallet; + +import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletUpdateBalanceReqDTO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; +import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import static cn.iocoder.yudao.framework.common.enums.UserTypeEnum.MEMBER; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.WALLET_NOT_FOUND; + +/** + * 会员钱包 API 实现类 + * + * @author HUIHUI + */ +@Service +@Validated +@Slf4j +public class PayWalletApiImpl implements PayWalletApi { + + @Resource + private PayWalletService payWalletService; + + @Override + public void updateBalance(PayWalletUpdateBalanceReqDTO reqDTO) { + // 获得用户钱包 + PayWalletDO wallet = payWalletService.getOrCreateWallet(reqDTO.getUserId(), MEMBER.getValue()); + if (wallet == null) { + log.error("[updateBalance],reqDTO({}) 用户钱包不存在.", reqDTO); + throw exception(WALLET_NOT_FOUND); + } + + // 更新钱包余额 + payWalletService.addWalletBalance(wallet.getId(), String.valueOf(reqDTO.getUserId()), + PayWalletBizTypeEnum.UPDATE_BALANCE, reqDTO.getBalance()); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java index 513786143..b844e3769 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java @@ -12,12 +12,12 @@ import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; import cn.iocoder.yudao.module.pay.service.order.PayOrderService; import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; import cn.iocoder.yudao.module.pay.service.wallet.bo.WalletTransactionCreateReqBO; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import jakarta.annotation.Resource; import java.time.LocalDateTime; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; @@ -176,6 +176,9 @@ public class PayWalletServiceImpl implements PayWalletService { walletMapper.updateWhenRecharge(payWallet.getId(), price); break; } + case UPDATE_BALANCE: // 更新余额 + walletMapper.updateWhenRecharge(payWallet.getId(), price); + break; default: { // TODO 其它类型待实现 throw new UnsupportedOperationException("待实现"); From 12a80ca4b8d95c62ca5e288baff64f8c11933e30 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 19 Aug 2024 19:51:23 +0800 Subject: [PATCH 08/42] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91PAY=EF=BC=9A=E9=92=B1=E5=8C=85=E4=BD=99?= =?UTF-8?q?=E9=A2=9D=E4=BF=AE=E6=94=B9=E7=9A=84=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-module-member/yudao-module-member-biz/pom.xml | 1 + .../cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java | 1 + .../module/pay/api/wallet/dto/PayWalletUpdateBalanceReqDTO.java | 1 + .../yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java | 2 -- .../iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java | 1 + 5 files changed, 4 insertions(+), 2 deletions(-) diff --git a/yudao-module-member/yudao-module-member-biz/pom.xml b/yudao-module-member/yudao-module-member-biz/pom.xml index 368a3cba7..165ab9f83 100644 --- a/yudao-module-member/yudao-module-member-biz/pom.xml +++ b/yudao-module-member/yudao-module-member-biz/pom.xml @@ -33,6 +33,7 @@ yudao-module-infra-api ${revision} + cn.iocoder.boot yudao-module-pay-api diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java index a99bafe93..7e7342f6b 100644 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.pay.api.wallet; import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletUpdateBalanceReqDTO; +// TODO @puhui999:不在 MemberUserController 提供接口,而是 PayWalletController 增加。不然 member 耦合 pay 拉。 /** * 会员钱包 API 接口 * diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletUpdateBalanceReqDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletUpdateBalanceReqDTO.java index 02a7756dd..f10de79c5 100644 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletUpdateBalanceReqDTO.java +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletUpdateBalanceReqDTO.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.pay.api.wallet.dto; import jakarta.validation.constraints.NotNull; import lombok.Data; +// TODO @puhui999:不在 MemberUserController 提供接口,而是 PayWalletController 增加。不然 member 耦合 pay 拉。 /** * 钱包余额更新 Request DTO * diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java index 7892db543..ae99128b9 100644 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java @@ -21,8 +21,6 @@ public enum PayWalletBizTypeEnum implements IntArrayValuable { PAYMENT_REFUND(4, "支付退款"), UPDATE_BALANCE(5, "更新余额"); - // TODO 后续增加 - /** * 业务分类 */ diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java index 9f94e1a8f..247a6c936 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java @@ -13,6 +13,7 @@ import static cn.iocoder.yudao.framework.common.enums.UserTypeEnum.MEMBER; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.WALLET_NOT_FOUND; +// @puhui999:不在 MemberUserController 提供接口,而是 PayWalletController 增加。不然 member 耦合 pay 拉。 /** * 会员钱包 API 实现类 * From 5c3a960403cec9ff736ce285202ed7ddad94e747 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 19 Aug 2024 19:51:32 +0800 Subject: [PATCH 09/42] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91PAY=EF=BC=9A=E9=92=B1=E5=8C=85=E4=BD=99?= =?UTF-8?q?=E9=A2=9D=E4=BF=AE=E6=94=B9=E7=9A=84=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/pay/controller/admin/wallet/PayWalletController.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java index 15e381538..273ea22f2 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java @@ -48,4 +48,6 @@ public class PayWalletController { return success(PayWalletConvert.INSTANCE.convertPage(pageResult)); } + // TODO @puhui999:修改钱包余额,权限标识,记得加下噢 + } From 91240351ed55fdf89a9b2e6c49f7b0274a98143a Mon Sep 17 00:00:00 2001 From: puhui999 Date: Tue, 20 Aug 2024 15:50:07 +0800 Subject: [PATCH 10/42] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E5=95=86=E5=9F=8E:=20=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E8=AF=84=E5=AE=A1=20TODO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/spu/dto/ProductSpuRespDTO.java | 9 ++++ .../TradeDeliveryPriceCalculator.java | 22 +++++++--- .../yudao-module-member-biz/pom.xml | 6 --- .../admin/user/MemberUserController.java | 14 ------ .../module/pay/api/wallet/PayWalletApi.java | 20 --------- .../dto/PayWalletUpdateBalanceReqDTO.java | 24 ---------- .../pay/api/wallet/PayWalletApiImpl.java | 44 ------------------- .../admin/wallet/PayWalletController.java | 25 +++++++++-- .../wallet/PayWalletUpdateBalanceReqVO.java | 10 ++--- 9 files changed, 50 insertions(+), 124 deletions(-) delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletUpdateBalanceReqDTO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java rename yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserUpdateBalanceReqVO.java => yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUpdateBalanceReqVO.java (67%) diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/dto/ProductSpuRespDTO.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/dto/ProductSpuRespDTO.java index 707ccc338..60fb0bcd1 100644 --- a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/dto/ProductSpuRespDTO.java +++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/dto/ProductSpuRespDTO.java @@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.product.api.spu.dto; import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; import lombok.Data; +import java.util.List; + /** * 商品 SPU 信息 Response DTO * @@ -68,6 +70,13 @@ public class ProductSpuRespDTO { // ========== 物流相关字段 ========= + /** + * 配送方式数组 + * + * 对应 DeliveryTypeEnum 枚举 + */ + private List deliveryTypes; + /** * 物流配置模板编号 * diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java index d9fed7aeb..9ab0fbabc 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java @@ -6,6 +6,8 @@ import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.module.member.api.address.MemberAddressApi; import cn.iocoder.yudao.module.member.api.address.dto.MemberAddressRespDTO; +import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; +import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO; import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryPickUpStoreDO; import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryExpressChargeModeEnum; @@ -17,18 +19,19 @@ import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplate import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO.OrderItem; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; -import jakarta.annotation.Resource; import java.util.List; import java.util.Map; import java.util.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; -import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PICK_UP_STORE_NOT_EXISTS; +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND; /** * 运费的 {@link TradePriceCalculator} 实现类 @@ -49,13 +52,20 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { private DeliveryExpressTemplateService deliveryExpressTemplateService; @Resource private TradeConfigService tradeConfigService; + @Resource + private ProductSpuApi productSpuApi; @Override public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { if (param.getDeliveryType() == null) { return; } - // TODO @puhui999:需要校验,是不是存在商品不能门店自提,或者不能快递发货的情况。就是说,配送方式不匹配哈 + // 校验是不是存在商品不能门店自提,或者不能快递发货的情况。就是说,配送方式不匹配哈 + List spuList = productSpuApi.getSpuList(convertSet(result.getItems(), OrderItem::getSpuId)); + if (anyMatch(spuList, item -> !item.getDeliveryTypes().contains(param.getDeliveryType()))) { + return; + } + if (DeliveryTypeEnum.PICK_UP.getType().equals(param.getDeliveryType())) { calculateByPickUp(param); } else if (DeliveryTypeEnum.EXPRESS.getType().equals(param.getDeliveryType())) { @@ -124,7 +134,7 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { Map> template2ItemMap = convertMultiMap(selectedSkus, OrderItem::getDeliveryTemplateId); // 依次计算快递运费 for (Map.Entry> entry : template2ItemMap.entrySet()) { - Long templateId = entry.getKey(); + Long templateId = entry.getKey(); List orderItems = entry.getValue(); DeliveryExpressTemplateRespBO templateBO = expressTemplateMap.get(templateId); if (templateBO == null) { @@ -144,8 +154,8 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { /** * 按配送方式来计算运费 * - * @param orderItems SKU 商品项目 - * @param chargeMode 配送计费方式 + * @param orderItems SKU 商品项目 + * @param chargeMode 配送计费方式 * @param templateCharge 快递运费配置 */ private void calculateExpressFeeByChargeMode(List orderItems, Integer chargeMode, diff --git a/yudao-module-member/yudao-module-member-biz/pom.xml b/yudao-module-member/yudao-module-member-biz/pom.xml index 165ab9f83..3c9b81e65 100644 --- a/yudao-module-member/yudao-module-member-biz/pom.xml +++ b/yudao-module-member/yudao-module-member-biz/pom.xml @@ -33,12 +33,6 @@ yudao-module-infra-api ${revision} - - - cn.iocoder.boot - yudao-module-pay-api - ${revision} - diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/MemberUserController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/MemberUserController.java index 09978f6e0..6a642bbf0 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/MemberUserController.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/MemberUserController.java @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.member.controller.admin.user; import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.member.controller.admin.user.vo.*; import cn.iocoder.yudao.module.member.convert.user.MemberUserConvert; import cn.iocoder.yudao.module.member.dal.dataobject.group.MemberGroupDO; @@ -16,8 +15,6 @@ import cn.iocoder.yudao.module.member.service.level.MemberLevelService; import cn.iocoder.yudao.module.member.service.point.MemberPointRecordService; import cn.iocoder.yudao.module.member.service.tag.MemberTagService; import cn.iocoder.yudao.module.member.service.user.MemberUserService; -import cn.iocoder.yudao.module.pay.api.wallet.PayWalletApi; -import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletUpdateBalanceReqDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -53,8 +50,6 @@ public class MemberUserController { private MemberGroupService memberGroupService; @Resource private MemberPointRecordService memberPointRecordService; - @Resource - private PayWalletApi payWalletApi; @PutMapping("/update") @Operation(summary = "更新会员用户") @@ -81,15 +76,6 @@ public class MemberUserController { return success(true); } - @PutMapping("/update-balance") - @Operation(summary = "更新会员用户余额") - @PreAuthorize("@ss.hasPermission('member:user:update-balance')") - public CommonResult updateUserBalance(@Valid @RequestBody MemberUserUpdateBalanceReqVO updateReqVO) { - payWalletApi.updateBalance(BeanUtils.toBean(updateReqVO, PayWalletUpdateBalanceReqDTO.class) - .setUserId(updateReqVO.getId())); - return success(true); - } - @GetMapping("/get") @Operation(summary = "获得会员用户") @Parameter(name = "id", description = "编号", required = true, example = "1024") diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java deleted file mode 100644 index 7e7342f6b..000000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java +++ /dev/null @@ -1,20 +0,0 @@ -package cn.iocoder.yudao.module.pay.api.wallet; - -import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletUpdateBalanceReqDTO; - -// TODO @puhui999:不在 MemberUserController 提供接口,而是 PayWalletController 增加。不然 member 耦合 pay 拉。 -/** - * 会员钱包 API 接口 - * - * @author HUIHUI - */ -public interface PayWalletApi { - - /** - * 更新钱包余额 - * - * @param reqDTO 请求 - */ - void updateBalance(PayWalletUpdateBalanceReqDTO reqDTO); - -} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletUpdateBalanceReqDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletUpdateBalanceReqDTO.java deleted file mode 100644 index f10de79c5..000000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletUpdateBalanceReqDTO.java +++ /dev/null @@ -1,24 +0,0 @@ -package cn.iocoder.yudao.module.pay.api.wallet.dto; - -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -// TODO @puhui999:不在 MemberUserController 提供接口,而是 PayWalletController 增加。不然 member 耦合 pay 拉。 -/** - * 钱包余额更新 Request DTO - * - * @author HUIHUI - */ -@Data -public class PayWalletUpdateBalanceReqDTO { - - @NotNull(message = "用户编号不能为空") - private Long userId; - - /** - * 变动余额,正数为增加,负数为减少 - */ - @NotNull(message = "变动余额不能为空") - private Integer balance; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java deleted file mode 100644 index 247a6c936..000000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java +++ /dev/null @@ -1,44 +0,0 @@ -package cn.iocoder.yudao.module.pay.api.wallet; - -import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletUpdateBalanceReqDTO; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; -import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; -import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -import static cn.iocoder.yudao.framework.common.enums.UserTypeEnum.MEMBER; -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.WALLET_NOT_FOUND; - -// @puhui999:不在 MemberUserController 提供接口,而是 PayWalletController 增加。不然 member 耦合 pay 拉。 -/** - * 会员钱包 API 实现类 - * - * @author HUIHUI - */ -@Service -@Validated -@Slf4j -public class PayWalletApiImpl implements PayWalletApi { - - @Resource - private PayWalletService payWalletService; - - @Override - public void updateBalance(PayWalletUpdateBalanceReqDTO reqDTO) { - // 获得用户钱包 - PayWalletDO wallet = payWalletService.getOrCreateWallet(reqDTO.getUserId(), MEMBER.getValue()); - if (wallet == null) { - log.error("[updateBalance],reqDTO({}) 用户钱包不存在.", reqDTO); - throw exception(WALLET_NOT_FOUND); - } - - // 更新钱包余额 - payWalletService.addWalletBalance(wallet.getId(), String.valueOf(reqDTO.getUserId()), - PayWalletBizTypeEnum.UPDATE_BALANCE, reqDTO.getBalance()); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java index 273ea22f2..54fa00419 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java @@ -4,9 +4,11 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletPageReqVO; import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletRespVO; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletUpdateBalanceReqVO; import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletUserReqVO; import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletConvert; import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; +import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -15,12 +17,12 @@ import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import static cn.iocoder.yudao.framework.common.enums.UserTypeEnum.MEMBER; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.WALLET_NOT_FOUND; @Tag(name = "管理后台 - 用户钱包") @RestController @@ -48,6 +50,21 @@ public class PayWalletController { return success(PayWalletConvert.INSTANCE.convertPage(pageResult)); } - // TODO @puhui999:修改钱包余额,权限标识,记得加下噢 + @PutMapping("/update-balance") + @Operation(summary = "更新会员用户余额") + @PreAuthorize("@ss.hasPermission('pay:wallet:update-balance')") + public CommonResult updateWalletBalance(@Valid @RequestBody PayWalletUpdateBalanceReqVO updateReqVO) { + // 获得用户钱包 + PayWalletDO wallet = payWalletService.getOrCreateWallet(updateReqVO.getUserId(), MEMBER.getValue()); + if (wallet == null) { + log.error("[updateWalletBalance],updateReqVO({}) 用户钱包不存在.", updateReqVO); + throw exception(WALLET_NOT_FOUND); + } + + // 更新钱包余额 + payWalletService.addWalletBalance(wallet.getId(), String.valueOf(updateReqVO.getUserId()), + PayWalletBizTypeEnum.UPDATE_BALANCE, updateReqVO.getBalance()); + return success(true); + } } diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserUpdateBalanceReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUpdateBalanceReqVO.java similarity index 67% rename from yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserUpdateBalanceReqVO.java rename to yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUpdateBalanceReqVO.java index fe694df67..7569bca78 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserUpdateBalanceReqVO.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUpdateBalanceReqVO.java @@ -1,18 +1,16 @@ -package cn.iocoder.yudao.module.member.controller.admin.user.vo; +package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import lombok.Data; -import lombok.ToString; -@Schema(description = "管理后台 - 用户修改余额 Request VO") +@Schema(description = "管理后台 - 修改钱包余额 Request VO") @Data -@ToString(callSuper = true) -public class MemberUserUpdateBalanceReqVO { +public class PayWalletUpdateBalanceReqVO { @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23788") @NotNull(message = "用户编号不能为空") - private Long id; + private Long userId; @Schema(description = "变动余额,正数为增加,负数为减少", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") @NotNull(message = "变动余额不能为空") From aff0446a3848df3e271a104bacdfb1fdfac57c3c Mon Sep 17 00:00:00 2001 From: puhui999 Date: Tue, 20 Aug 2024 16:12:24 +0800 Subject: [PATCH 11/42] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E5=95=86=E5=9F=8E:=20=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E5=8F=96=E6=B6=88=E6=94=AF=E4=BB=98=E8=AE=A2=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../trade/enums/ErrorCodeConstants.java | 1 + .../order/TradeOrderUpdateServiceImpl.java | 24 ++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java index 33081d461..696eeba1b 100644 --- a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java +++ b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java @@ -35,6 +35,7 @@ public interface ErrorCodeConstants { ErrorCode ORDER_RECEIVE_FAIL_DELIVERY_TYPE_NOT_PICK_UP = new ErrorCode(1_011_000_030, "交易订单自提失败,收货方式不是【用户自提】"); ErrorCode ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED = new ErrorCode(1_011_000_031, "交易订单修改收货地址失败,原因:订单不是【待发货】状态"); ErrorCode ORDER_CREATE_FAIL_EXIST_UNPAID = new ErrorCode(1_011_000_032, "交易订单创建失败,原因:存在未付款订单"); + ErrorCode ORDER_CANCEL_PAID_FAIL = new ErrorCode(1_011_000_033, "交易订单取消支付失败,原因:订单不是【{}】状态"); // ========== After Sale 模块 1-011-000-100 ========== ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1_011_000_100, "售后单不存在"); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java index 945e36fc5..3a8d65fc8 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java @@ -18,6 +18,8 @@ import cn.iocoder.yudao.module.member.api.address.dto.MemberAddressRespDTO; import cn.iocoder.yudao.module.pay.api.order.PayOrderApi; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi; +import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO; import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; import cn.iocoder.yudao.module.product.api.comment.ProductCommentApi; import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO; @@ -111,6 +113,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { private ProductCommentApi productCommentApi; @Resource public SocialClientApi socialClientApi; + @Resource + public PayRefundApi payRefundApi; @Resource private TradeOrderProperties tradeOrderProperties; @@ -855,14 +859,28 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @Override @Transactional(rollbackFor = Exception.class) public void cancelPaidOrder(Long userId, Long orderId) { - // TODO @puhui999:需要校验状态;已支付的情况下,才可以。 + // 1.1 检验订单存在 TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(orderId, userId); if (order == null) { throw exception(ORDER_NOT_FOUND); } - cancelOrder0(order, TradeOrderCancelTypeEnum.MEMBER_CANCEL); + // 1.2 校验订单是否支付 + if (!order.getPayStatus()) { + throw exception(ORDER_CANCEL_PAID_FAIL, "已支付"); + } + // 1.3 校验订单是否已退款 + if (ObjUtil.equal(TradeOrderRefundStatusEnum.NONE.getStatus(), order.getRefundStatus())) { + throw exception(ORDER_CANCEL_PAID_FAIL, "未退款"); + } - // TODO @puhui999:需要退款 + // 2.1 取消订单 + cancelOrder0(order, TradeOrderCancelTypeEnum.AFTER_SALE_CLOSE); + // 2.2 创建退款单 + payRefundApi.createRefund(new PayRefundCreateReqDTO() + .setAppKey(tradeOrderProperties.getPayAppKey()).setUserIp(getClientIP()) // 支付应用 + .setMerchantOrderId(String.valueOf(order.getId())) // 支付单号 + .setMerchantRefundId(String.valueOf(order.getId())) + .setReason("取消支付订单").setPrice(order.getPayPrice()));// 价格信息 } /** From 2b681f90ca2cf4a06cc97234f7c20d6d2a80175c Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 20 Aug 2024 21:18:56 +0800 Subject: [PATCH 12/42] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91=E5=95=86=E5=9F=8E=EF=BC=9A=E6=8B=BC=E5=9B=A2?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E7=9A=84=E5=8F=96=E6=B6=88=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java | 1 - .../module/trade/service/order/TradeOrderUpdateServiceImpl.java | 1 + .../service/price/calculator/TradeDeliveryPriceCalculator.java | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java index 744a7b8fd..f36f7bc95 100644 --- a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java +++ b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java @@ -28,7 +28,6 @@ public interface TradeOrderApi { */ TradeOrderRespDTO getOrder(Long id); - // TODO 芋艿:需要优化下; /** * 取消支付订单 * diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java index 3a8d65fc8..c005781e3 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java @@ -859,6 +859,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @Override @Transactional(rollbackFor = Exception.class) public void cancelPaidOrder(Long userId, Long orderId) { + // TODO @puhui999:可能要加一个拼团取消;TradeOrderCancelTypeEnum.AFTER_SALE_CLOSE;然后参数传入下; // 1.1 检验订单存在 TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(orderId, userId); if (order == null) { diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java index 9ab0fbabc..2fa0d44af 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java @@ -60,6 +60,7 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { if (param.getDeliveryType() == null) { return; } + // TODO @puhui999:1)TradePriceCalculateRespBO 传递进来 delveryType 配送方式,减少读取;2)如果不匹配,抛出业务异常; = = 不然就不扣钱啦。 // 校验是不是存在商品不能门店自提,或者不能快递发货的情况。就是说,配送方式不匹配哈 List spuList = productSpuApi.getSpuList(convertSet(result.getItems(), OrderItem::getSpuId)); if (anyMatch(spuList, item -> !item.getDeliveryTypes().contains(param.getDeliveryType()))) { From 32e25cf4a24dd855a741832d98677863dff2091e Mon Sep 17 00:00:00 2001 From: puhui999 Date: Thu, 22 Aug 2024 18:23:54 +0800 Subject: [PATCH 13/42] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E5=95=86=E5=9F=8E:=20=E6=BB=A1=E5=87=8F?= =?UTF-8?q?=E9=80=81=E6=B4=BB=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/PromotionProductScopeEnum.java | 7 ++- .../admin/reward/vo/RewardActivityBaseVO.java | 40 +++++++++++----- .../app/activity/AppActivityController.java | 47 +++++++++---------- .../dataobject/reward/RewardActivityDO.java | 10 +++- .../reward/RewardActivityServiceImpl.java | 43 +++++++++++++---- .../aftersale/vo/AfterSalePageReqVO.java | 3 -- .../dal/mysql/aftersale/AfterSaleMapper.java | 1 - 7 files changed, 96 insertions(+), 55 deletions(-) diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/common/PromotionProductScopeEnum.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/common/PromotionProductScopeEnum.java index 882dc4aee..98e2ac7c9 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/common/PromotionProductScopeEnum.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/common/PromotionProductScopeEnum.java @@ -15,10 +15,9 @@ import java.util.Arrays; @AllArgsConstructor public enum PromotionProductScopeEnum implements IntArrayValuable { - ALL(1, "通用券"), // 全部商品 - SPU(2, "商品券"), // 指定商品 - CATEGORY(3, "品类券"), // 指定品类 - ; + ALL(1, "全部商品"), + SPU(2, "指定商品"), + CATEGORY(3, "指定品类"); public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(PromotionProductScopeEnum::getScope).toArray(); diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java index ae7a9f0bd..c6bb4eae1 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java @@ -1,23 +1,22 @@ package cn.iocoder.yudao.module.promotion.controller.admin.reward.vo; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.BooleanUtil; import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum; import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; import com.fasterxml.jackson.annotation.JsonIgnore; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import org.springframework.format.annotation.DateTimeFormat; - import jakarta.validation.Valid; import jakarta.validation.constraints.AssertTrue; import jakarta.validation.constraints.Future; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; +import lombok.Data; + import java.time.LocalDateTime; import java.util.List; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; +import java.util.Objects; /** * 满减送活动 Base VO,提供给添加、修改、详细的子 VO 使用 @@ -32,12 +31,10 @@ public class RewardActivityBaseVO { @Schema(description = "开始时间", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull(message = "开始时间不能为空") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private LocalDateTime startTime; @Schema(description = "结束时间", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull(message = "结束时间不能为空") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) @Future(message = "结束时间必须大于当前时间") private LocalDateTime endTime; @@ -54,8 +51,8 @@ public class RewardActivityBaseVO { @InEnum(value = PromotionProductScopeEnum.class, message = "商品范围必须是 {value}") private Integer productScope; - @Schema(description = "商品 SPU 编号的数组", example = "1,2,3") - private List productSpuIds; + @Schema(description = "商品范围编号的数组", example = "[1, 3]") + private List productScopeValues; /** * 优惠规则的数组 @@ -63,6 +60,13 @@ public class RewardActivityBaseVO { @Valid // 校验下子对象 private List rules; + @AssertTrue(message = "商品范围编号的数组不能为空") + @JsonIgnore + public boolean isProductScopeValuesValid() { + return Objects.equals(productScope, PromotionProductScopeEnum.ALL.getScope()) // 全部范围时,可以为空 + || CollUtil.isNotEmpty(productScopeValues); + } + @Schema(description = "优惠规则") @Data public static class Rule { @@ -76,12 +80,20 @@ public class RewardActivityBaseVO { private Integer discountPrice; @Schema(description = "是否包邮", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @NotNull(message = "规则是否包邮不能为空") private Boolean freeDelivery; + @Schema(description = "是否赠送积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @NotNull(message = "规则是否赠送积分不能为空") + private Boolean givePoint; + @Schema(description = "赠送的积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") - @Min(value = 1L, message = "赠送的积分必须大于等于 1") private Integer point; + @Schema(description = "是否赠送优惠券", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @NotNull(message = "规则是否赠送优惠券不能为空") + private Boolean giveCoupon; + @Schema(description = "赠送的优惠劵编号的数组", example = "1,2,3") private List couponIds; @@ -91,7 +103,13 @@ public class RewardActivityBaseVO { @AssertTrue(message = "优惠劵和数量必须一一对应") @JsonIgnore public boolean isCouponCountsValid() { - return CollUtil.size(couponCounts) == CollUtil.size(couponCounts); + return BooleanUtil.isFalse(givePoint) || CollUtil.size(couponIds) == CollUtil.size(couponCounts); + } + + @AssertTrue(message = "赠送的积分不能小于 1") + @JsonIgnore + public boolean isPointValid() { + return BooleanUtil.isFalse(givePoint) || (point != null && point >= 1); } } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/AppActivityController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/AppActivityController.java index 4ec685aab..a59ff7df1 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/AppActivityController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/AppActivityController.java @@ -9,9 +9,7 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityD import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO; import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountActivityDO; import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO; -import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO; import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO; -import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum; import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum; import cn.iocoder.yudao.module.promotion.service.bargain.BargainActivityService; import cn.iocoder.yudao.module.promotion.service.combination.CombinationActivityService; @@ -30,7 +28,6 @@ import org.springframework.web.bind.annotation.RestController; import java.time.LocalDateTime; import java.util.*; -import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; @@ -145,28 +142,28 @@ public class AppActivityController { } private void getRewardActivities(Collection spuIds, LocalDateTime now, List activityList) { - // TODO @puhui999:有 3 范围,不只 spuId,还有 categoryId,全部 - List rewardActivityList = rewardActivityService.getRewardActivityBySpuIdsAndStatusAndDateTimeLt( - spuIds, PromotionActivityStatusEnum.RUN.getStatus(), now); - if (CollUtil.isEmpty(rewardActivityList)) { - return; - } - - Map> spuIdAndActivityMap = spuIds.stream() - .collect(Collectors.toMap( - spuId -> spuId, - spuId -> rewardActivityList.stream() - .filter(activity -> activity.getProductSpuIds().contains(spuId)) - .max(Comparator.comparing(RewardActivityDO::getCreateTime)))); - for (Long supId : spuIdAndActivityMap.keySet()) { - if (spuIdAndActivityMap.get(supId).isEmpty()) { - continue; - } - - RewardActivityDO rewardActivityDO = spuIdAndActivityMap.get(supId).get(); - activityList.add(new AppActivityRespVO(rewardActivityDO.getId(), PromotionTypeEnum.REWARD_ACTIVITY.getType(), - rewardActivityDO.getName(), supId, rewardActivityDO.getStartTime(), rewardActivityDO.getEndTime())); - } + // TODO @puhui999:有 3 范围,不只 spuId,还有 categoryId,全部,下次 fix + //List rewardActivityList = rewardActivityService.getRewardActivityBySpuIdsAndStatusAndDateTimeLt( + // spuIds, PromotionActivityStatusEnum.RUN.getStatus(), now); + //if (CollUtil.isEmpty(rewardActivityList)) { + // return; + //} + // + //Map> spuIdAndActivityMap = spuIds.stream() + // .collect(Collectors.toMap( + // spuId -> spuId, + // spuId -> rewardActivityList.stream() + // .filter(activity -> activity.getProductSpuIds().contains(spuId)) + // .max(Comparator.comparing(RewardActivityDO::getCreateTime)))); + //for (Long supId : spuIdAndActivityMap.keySet()) { + // if (spuIdAndActivityMap.get(supId).isEmpty()) { + // continue; + // } + // + // RewardActivityDO rewardActivityDO = spuIdAndActivityMap.get(supId).get(); + // activityList.add(new AppActivityRespVO(rewardActivityDO.getId(), PromotionTypeEnum.REWARD_ACTIVITY.getType(), + // rewardActivityDO.getName(), supId, rewardActivityDO.getStartTime(), rewardActivityDO.getEndTime())); + //} } } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java index d94533e8c..507225ffd 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java @@ -71,7 +71,7 @@ public class RewardActivityDO extends BaseDO { * 商品 SPU 编号的数组 */ @TableField(typeHandler = LongListTypeHandler.class) - private List productSpuIds; + private List productScopeValues; /** * 优惠规则的数组 */ @@ -99,10 +99,18 @@ public class RewardActivityDO extends BaseDO { * 是否包邮 */ private Boolean freeDelivery; + /** + * 是否赠送积分 + */ + private Boolean givePoint; /** * 赠送的积分 */ private Integer point; + /** + * 是否赠送优惠券 + */ + private Boolean giveCoupon; /** * 赠送的优惠劵编号的数组 */ diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java index e896eab92..855d72c34 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java @@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.promotion.service.reward; import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.product.api.category.ProductCategoryApi; +import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO; import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityCreateReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO; @@ -10,6 +12,7 @@ import cn.iocoder.yudao.module.promotion.convert.reward.RewardActivityConvert; import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO; import cn.iocoder.yudao.module.promotion.dal.mysql.reward.RewardActivityMapper; import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum; +import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; import cn.iocoder.yudao.module.promotion.util.PromotionUtils; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; @@ -19,6 +22,7 @@ import java.time.LocalDateTime; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Objects; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; @@ -37,12 +41,19 @@ public class RewardActivityServiceImpl implements RewardActivityService { @Resource private RewardActivityMapper rewardActivityMapper; + @Resource + private ProductCategoryApi productCategoryApi; + @Resource + private ProductSpuApi productSpuApi; + @Override public Long createRewardActivity(RewardActivityCreateReqVO createReqVO) { - // 校验商品是否冲突 - validateRewardActivitySpuConflicts(null, createReqVO.getProductSpuIds()); + // 1.1 校验商品范围 + validateProductScope(createReqVO.getProductScope(), createReqVO.getProductScopeValues()); + // 1.2 校验商品是否冲突 + //validateRewardActivitySpuConflicts(null, createReqVO.getProductSpuIds()); - // 插入 + // 2. 插入 RewardActivityDO rewardActivity = RewardActivityConvert.INSTANCE.convert(createReqVO) .setStatus(PromotionUtils.calculateActivityStatus(createReqVO.getEndTime())); rewardActivityMapper.insert(rewardActivity); @@ -52,15 +63,17 @@ public class RewardActivityServiceImpl implements RewardActivityService { @Override public void updateRewardActivity(RewardActivityUpdateReqVO updateReqVO) { - // 校验存在 + // 1.1 校验存在 RewardActivityDO dbRewardActivity = validateRewardActivityExists(updateReqVO.getId()); if (dbRewardActivity.getStatus().equals(PromotionActivityStatusEnum.CLOSE.getStatus())) { // 已关闭的活动,不能修改噢 throw exception(REWARD_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED); } - // 校验商品是否冲突 - validateRewardActivitySpuConflicts(updateReqVO.getId(), updateReqVO.getProductSpuIds()); + // 1.2 校验商品范围 + validateProductScope(updateReqVO.getProductScope(), updateReqVO.getProductScopeValues()); + // 1.3 校验商品是否冲突 + //validateRewardActivitySpuConflicts(updateReqVO.getId(), updateReqVO.getProductSpuIds()); - // 更新 + // 2. 更新 RewardActivityDO updateObj = RewardActivityConvert.INSTANCE.convert(updateReqVO) .setStatus(PromotionUtils.calculateActivityStatus(updateReqVO.getEndTime())); rewardActivityMapper.updateById(updateObj); @@ -103,7 +116,7 @@ public class RewardActivityServiceImpl implements RewardActivityService { } // TODO @芋艿:逻辑有问题,需要优化;要分成全场、和指定来校验; - + // TODO @puhui999: 下次提交 fix /** * 校验商品参加的活动是否冲突 * @@ -126,6 +139,14 @@ public class RewardActivityServiceImpl implements RewardActivityService { } } + private void validateProductScope(Integer productScope, List productScopeValues) { + if (Objects.equals(PromotionProductScopeEnum.SPU.getScope(), productScope)) { + productSpuApi.validateSpuList(productScopeValues); + } else if (Objects.equals(PromotionProductScopeEnum.CATEGORY.getScope(), productScope)) { + productCategoryApi.validateCategoryList(productScopeValues); + } + } + /** * 获得商品参加的满减送活动的数组 * @@ -135,8 +156,10 @@ public class RewardActivityServiceImpl implements RewardActivityService { */ private List getRewardActivityListBySpuIds(Collection spuIds, Collection statuses) { - List list = rewardActivityMapper.selectListByStatus(statuses); - return CollUtil.filter(list, activity -> CollUtil.containsAny(activity.getProductSpuIds(), spuIds)); + // TODO @puhui999: 下次 fix + //List list = rewardActivityMapper.selectListByStatus(statuses); + //return CollUtil.filter(list, activity -> CollUtil.containsAny(activity.getProductSpuIds(), spuIds)); + return List.of(); } @Override diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/AfterSalePageReqVO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/AfterSalePageReqVO.java index 119370ace..4b8756c7b 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/AfterSalePageReqVO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/AfterSalePageReqVO.java @@ -27,9 +27,6 @@ public class AfterSalePageReqVO extends PageParam { @Schema(description = "售后流水号", example = "202211190847450020500077") private String no; - @Schema(description = "用户编号", example = "1024") - private Long userId; - @Schema(description = "售后状态", example = "10") @InEnum(value = AfterSaleStatusEnum.class, message = "售后状态必须是 {value}") private Integer status; diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleMapper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleMapper.java index 846bb31f9..341dabc45 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleMapper.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleMapper.java @@ -18,7 +18,6 @@ public interface AfterSaleMapper extends BaseMapperX { return selectPage(reqVO, new LambdaQueryWrapperX() .eqIfPresent(AfterSaleDO::getUserId, reqVO.getUserId()) .likeIfPresent(AfterSaleDO::getNo, reqVO.getNo()) - .eqIfPresent(AfterSaleDO::getUserId, reqVO.getUserId()) .eqIfPresent(AfterSaleDO::getStatus, reqVO.getStatus()) .eqIfPresent(AfterSaleDO::getType, reqVO.getType()) .eqIfPresent(AfterSaleDO::getWay, reqVO.getWay()) From f5706972a09c1f1aa47952144734ed3c63677740 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Fri, 23 Aug 2024 19:08:10 +0800 Subject: [PATCH 14/42] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91=E5=95=86=E5=9F=8E=EF=BC=9A=E6=BB=A1=E5=87=8F?= =?UTF-8?q?=E9=80=81=E6=B4=BB=E5=8A=A8=E7=9A=84=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/reward/vo/RewardActivityBaseVO.java | 14 +++++++------- .../dal/dataobject/reward/RewardActivityDO.java | 2 ++ .../service/reward/RewardActivityServiceImpl.java | 1 + .../reward/RewardActivityServiceImplTest.java | 1 + 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java index c6bb4eae1..d498b5e9f 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java @@ -60,13 +60,6 @@ public class RewardActivityBaseVO { @Valid // 校验下子对象 private List rules; - @AssertTrue(message = "商品范围编号的数组不能为空") - @JsonIgnore - public boolean isProductScopeValuesValid() { - return Objects.equals(productScope, PromotionProductScopeEnum.ALL.getScope()) // 全部范围时,可以为空 - || CollUtil.isNotEmpty(productScopeValues); - } - @Schema(description = "优惠规则") @Data public static class Rule { @@ -114,4 +107,11 @@ public class RewardActivityBaseVO { } + @AssertTrue(message = "商品范围编号的数组不能为空") + @JsonIgnore + public boolean isProductScopeValuesValid() { + return Objects.equals(productScope, PromotionProductScopeEnum.ALL.getScope()) // 全部范围时,可以为空 + || CollUtil.isNotEmpty(productScopeValues); + } + } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java index 507225ffd..98d3e8d81 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java @@ -99,6 +99,7 @@ public class RewardActivityDO extends BaseDO { * 是否包邮 */ private Boolean freeDelivery; + // TODO @puhui999:是不是大于零,就认为赠送积分哈;简洁一点; /** * 是否赠送积分 */ @@ -107,6 +108,7 @@ public class RewardActivityDO extends BaseDO { * 赠送的积分 */ private Integer point; + // TODO @puhui999:非空,就认为赠送优惠劵 /** * 是否赠送优惠券 */ diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java index 855d72c34..98fa990c1 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java @@ -82,6 +82,7 @@ public class RewardActivityServiceImpl implements RewardActivityService { @Override public void closeRewardActivity(Long id) { // 校验存在 + // TODO @puhui999:去掉 PromotionActivityStatusEnum,使用 CommonStatus 作为状态哈。开启,关闭 RewardActivityDO dbRewardActivity = validateRewardActivityExists(id); if (dbRewardActivity.getStatus().equals(PromotionActivityStatusEnum.CLOSE.getStatus())) { // 已关闭的活动,不能关闭噢 throw exception(REWARD_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED); diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImplTest.java b/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImplTest.java index f2297abf6..ca8d85fa7 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImplTest.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImplTest.java @@ -190,6 +190,7 @@ public class RewardActivityServiceImplTest extends BaseDbUnitTest { @Test public void testGetRewardActivities_product() { // mock 数据 + // TODO @puhui999:有单测的问题,也一起瞅瞅 RewardActivityDO productActivity01 = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.RUN.getStatus()) .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductSpuIds(asList(1L, 2L))); rewardActivityMapper.insert(productActivity01); From 4e1d7d0877b84eb50de4227023d4b59576287f18 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Fri, 23 Aug 2024 19:49:40 +0800 Subject: [PATCH 15/42] =?UTF-8?q?=E3=80=90=E4=BC=98=E5=8C=96=E3=80=91?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=94=9F=E6=88=90=EF=BC=9A=E9=92=88=E5=AF=B9?= =?UTF-8?q?=20element-plus=20=E7=9A=84=20checkbox=E3=80=81radio=20?= =?UTF-8?q?=E7=9A=84=20label=20=3D>=20value=20=E7=9A=84=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vue3/views/components/form_sub_erp.vue.vm | 11 +++++----- .../views/components/form_sub_normal.vue.vm | 22 +++++++++---------- .../resources/codegen/vue3/views/form.vue.vm | 11 +++++----- 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/components/form_sub_erp.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/components/form_sub_erp.vue.vm index 3996a9caa..81cd9775e 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/components/form_sub_erp.vue.vm +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/components/form_sub_erp.vue.vm @@ -64,12 +64,11 @@ - {{ dict.label }} - + :label="dict.label" + :value="dict.value" + /> #else##没数据字典 - 请选择字典生成 + #end @@ -85,7 +84,7 @@ {{ dict.label }} #else##没数据字典 - 请选择字典生成 + 请选择字典生成 #end diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/components/form_sub_normal.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/components/form_sub_normal.vue.vm index dbd03569e..3fa1effb2 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/components/form_sub_normal.vue.vm +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/components/form_sub_normal.vue.vm @@ -92,12 +92,11 @@ - {{ dict.label }} - + :label="dict.label" + :value="dict.value" + /> #else##没数据字典 - 请选择字典生成 + #end @@ -117,7 +116,7 @@ {{ dict.label }} #else##没数据字典 - 请选择字典生成 + 请选择字典生成 #end @@ -219,12 +218,11 @@ - {{ dict.label }} - + :label="dict.label" + :value="dict.value" + /> #else##没数据字典 - 请选择字典生成 + #end @@ -240,7 +238,7 @@ {{ dict.label }} #else##没数据字典 - 请选择字典生成 + 请选择字典生成 #end diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/form.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/form.vue.vm index 8e3596b4f..e37474b85 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/form.vue.vm +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/form.vue.vm @@ -75,12 +75,11 @@ - {{ dict.label }} - + :label="dict.label" + :value="dict.value" + /> #else##没数据字典 - 请选择字典生成 + #end @@ -96,7 +95,7 @@ {{ dict.label }} #else##没数据字典 - 请选择字典生成 + 请选择字典生成 #end From 02562cb77d46aa8cbfaac7e32066419341bcd209 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Fri, 23 Aug 2024 20:01:32 +0800 Subject: [PATCH 16/42] =?UTF-8?q?bugfix:=20S3=20=E5=AE=A2=E6=9C=8D?= =?UTF-8?q?=E7=AB=AF=20VirtualStyle=20=E5=88=A4=E6=96=AD=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infra/framework/file/core/client/s3/S3FileClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java index 43ff2733b..29f6fc34f 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java @@ -91,7 +91,7 @@ public class S3FileClient extends AbstractFileClient { * 开启 VirtualStyle 模式 */ private void enableVirtualStyleEndpoint() { - if (StrUtil.containsAll(config.getEndpoint(), + if (StrUtil.containsAny(config.getEndpoint(), S3FileClientConfig.ENDPOINT_TENCENT, // 腾讯云 https://cloud.tencent.com/document/product/436/41284 S3FileClientConfig.ENDPOINT_VOLCES)) { // 火山云 https://www.volcengine.com/docs/6349/1288493 client.enableVirtualStyleEndpoint(); From 7384b5c3f6d6fada19bf8927375c6f59e7f81b8e Mon Sep 17 00:00:00 2001 From: YunaiV Date: Fri, 23 Aug 2024 20:06:36 +0800 Subject: [PATCH 17/42] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E3=80=91=E5=95=86=E5=9F=8E=EF=BC=9A=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E8=AF=84=E4=BB=B7=E5=81=B6=E7=8E=B0=E8=AE=A2=E5=8D=95=E5=95=86?= =?UTF-8?q?=E5=93=81=E5=B7=B2=E8=AF=84=E4=BB=B7=EF=BC=88=E5=AE=9E=E9=99=85?= =?UTF-8?q?=E6=9C=AA=E8=AF=84=E4=BB=B7=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/service/comment/ProductCommentServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImpl.java index 83c8e93a1..f12345416 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImpl.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImpl.java @@ -67,7 +67,7 @@ public class ProductCommentServiceImpl implements ProductCommentService { // 校验 SPU ProductSpuDO spu = validateSpu(sku.getSpuId()); // 校验评论 - validateCommentExists(createReqDTO.getUserId(), createReqDTO.getOrderId()); + validateCommentExists(createReqDTO.getUserId(), createReqDTO.getOrderItemId()); // 获取用户详细信息 MemberUserRespDTO user = memberUserApi.getUser(createReqDTO.getUserId()); From 4f7ac969feec4fc61f403f3aa2e605473900e544 Mon Sep 17 00:00:00 2001 From: scholar <1145227973@qq.com> Date: Mon, 26 Aug 2024 15:52:41 +0800 Subject: [PATCH 18/42] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=83=E7=89=9B?= =?UTF-8?q?=E4=BA=91=E7=9F=AD=E4=BF=A1=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../framework/common/util/http/HttpUtils.java | 42 ++++- .../admin/sms/SmsCallbackController.java | 17 +- .../sms/core/client/impl/QiniuSmsClient.java | 172 ++++++++++++++++++ .../sms/core/enums/SmsChannelEnum.java | 2 + .../core/client/impl/QiniuSmsClientTest.java | 128 +++++++++++++ .../sms/core/client/impl/SmsClientTests.java | 138 +++++++------- 6 files changed, 428 insertions(+), 71 deletions(-) create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClient.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClientTest.java diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java index 9a39a7a4e..1697d097f 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java @@ -5,6 +5,8 @@ import cn.hutool.core.map.TableMap; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; @@ -109,7 +111,7 @@ public class HttpUtils { authorization = Base64.decodeStr(authorization); clientId = StrUtil.subBefore(authorization, ":", false); clientSecret = StrUtil.subAfter(authorization, ":", false); - // 再从 Param 中获取 + // 再从 Param 中获取 } else { clientId = request.getParameter("client_id"); clientSecret = request.getParameter("client_secret"); @@ -122,5 +124,43 @@ public class HttpUtils { return null; } + /** + * HTTP post 请求,基于 {@link cn.hutool.http.HttpUtil} 实现 + * + * 为什么要封装该方法,因为 HttpUtil 默认封装的方法,没有允许传递 headers 参数 + * + * @param url URL + * @param headers 请求头 + * @param requestBody 请求体 + * @return 请求结果 + */ + public static String post(String url, Map headers, String requestBody) { + + try (HttpResponse response = HttpRequest.post(url) + .addHeaders(headers) + .body(requestBody) + .execute()) { + return response.body(); + } + } + + /** + * HTTP get 请求,基于 {@link cn.hutool.http.HttpUtil} 实现 + * + * 为什么要封装该方法,因为 HttpUtil 默认封装的方法,没有允许传递 headers 参数 + * + * @param url URL + * @param headers 请求头 + * @return 请求结果 + */ + public static String get(String url, Map headers) { + + try (HttpResponse response = HttpRequest.get(url) + .addHeaders(headers) + .execute()) { + return response.body(); + } + } } + diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java index 90cb763cc..f4712f0ab 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java @@ -2,10 +2,8 @@ package cn.iocoder.yudao.module.system.controller.admin.sms; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; -import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsChannelEnum; import cn.iocoder.yudao.module.system.service.sms.SmsSendService; -import com.xingyuv.captcha.util.StreamUtils; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.web.bind.annotation.*; @@ -14,8 +12,6 @@ import jakarta.annotation.Resource; import jakarta.annotation.security.PermitAll; import jakarta.servlet.http.HttpServletRequest; -import java.nio.charset.Charset; - import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @Tag(name = "管理后台 - 短信回调") @@ -29,7 +25,6 @@ public class SmsCallbackController { @PostMapping("/aliyun") @PermitAll @Operation(summary = "阿里云短信的回调", description = "参见 https://help.aliyun.com/document_detail/120998.html 文档") - @OperateLog(enable = false) public CommonResult receiveAliyunSmsStatus(HttpServletRequest request) throws Throwable { String text = ServletUtils.getBody(request); smsSendService.receiveSmsStatus(SmsChannelEnum.ALIYUN.getCode(), text); @@ -39,7 +34,6 @@ public class SmsCallbackController { @PostMapping("/tencent") @PermitAll @Operation(summary = "腾讯云短信的回调", description = "参见 https://cloud.tencent.com/document/product/382/52077 文档") - @OperateLog(enable = false) public CommonResult receiveTencentSmsStatus(HttpServletRequest request) throws Throwable { String text = ServletUtils.getBody(request); smsSendService.receiveSmsStatus(SmsChannelEnum.TENCENT.getCode(), text); @@ -50,9 +44,18 @@ public class SmsCallbackController { @PostMapping("/huawei") @PermitAll @Operation(summary = "华为云短信的回调", description = "参见 https://support.huaweicloud.com/api-msgsms/sms_05_0003.html 文档") - @OperateLog(enable = false) public CommonResult receiveHuaweiSmsStatus(@RequestBody String requestBody) throws Throwable { smsSendService.receiveSmsStatus(SmsChannelEnum.HUAWEI.getCode(), requestBody); return success(true); } + + @PostMapping("/qiniu") + @PermitAll + @Operation(summary = "七牛云短信的回调", description = "参见 https://developer.qiniu.com/sms/5910/message-push 文档") + public CommonResult receiveQiniuSmsStatus(@RequestBody String requestBody) throws Throwable { + smsSendService.receiveSmsStatus(SmsChannelEnum.QINIU.getCode(), requestBody); + return success(true); + } + } + diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClient.java new file mode 100644 index 000000000..c0a2b60ac --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClient.java @@ -0,0 +1,172 @@ +package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; + +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.crypto.digest.HMac; +import cn.hutool.crypto.digest.HmacAlgorithm; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import cn.iocoder.yudao.framework.common.core.KeyValue; +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; +import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; +import com.google.common.annotations.VisibleForTesting; +import lombok.extern.slf4j.Slf4j; + +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 七牛云短信客户端的实现类 + * + * @author scholar + * @since 2024/08/26 15:35 + */ +@Slf4j +public class QiniuSmsClient extends AbstractSmsClient { + + private static final String HOST = "sms.qiniuapi.com"; + + private static final String PATH = "/v1/message/single"; + + private static final String TEMPLATE_PATH = "/v1/template"; + + public QiniuSmsClient(SmsChannelProperties properties) { + super(properties); + Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空"); + Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); + } + + @Override + protected void doInit() { + } + @Override + public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, + List> templateParams) throws Throwable { + + // 1. 执行请求 + // 参考链接 https://developer.qiniu.com/sms/5824/through-the-api-send-text-messages + LinkedHashMap body = new LinkedHashMap<>(); + Map paramsMap = templateParams.stream() + .collect(Collectors.toMap(KeyValue::getKey, KeyValue::getValue)); + + body.put("template_id", apiTemplateId); + body.put("mobile", mobile); + body.put("parameters", paramsMap); + body.put("seq", Long.toString(sendLogId)); + + JSONObject response = request("POST", body, null); + // 2. 解析请求 + return new SmsSendRespDTO().setSuccess(response.containsKey("message_id")) + .setSerialNo(response.getStr("message_id")); + } + + + /** + * 请求七牛云短信 + * + * @see + * @param httpMethod http请求方法 + * @param queryParams 请求参数 + * @return 请求结果 + */ + private JSONObject request(String httpMethod, LinkedHashMap body, Map queryParams) { + + String signature = ""; + String templateIdPath = ""; + + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + String signDate = dateFormat.format(new Date()); + + //请求头 + Map header = new HashMap<>(4); + header.put("HOST", HOST); + header.put("Authorization", signature); + header.put("Content-Type", "application/json"); + header.put("X-Qiniu-Date", signDate); + + String responseBody =""; + if(Objects.equals(httpMethod, "POST")){ + header.put("Authorization", getSignature(httpMethod, HOST, PATH, JSONUtil.toJsonStr(body), signDate)); + responseBody = HttpUtils.post("https://" + HOST + PATH, header, JSONUtil.toJsonStr(body)); + }else { // GET + templateIdPath = TEMPLATE_PATH + "/" + queryParams.get("template_id"); + header.put("Authorization", getSignature(httpMethod, HOST, templateIdPath, null, signDate)); + responseBody = HttpUtils.get("https://" + HOST + templateIdPath, header); + } + return JSONUtil.parseObj(responseBody); + } + + public String getSignature(String method, String host, String path, String body, String signDate) { + + StringBuilder dataToSign = new StringBuilder(); + dataToSign.append(method.toUpperCase()).append(" ").append(path); + dataToSign.append("\nHost: ").append(host); + dataToSign.append("\n").append("Content-Type").append(": ").append("application/json"); + dataToSign.append("\n").append("X-Qiniu-Date").append(": ").append(signDate); + dataToSign.append("\n\n"); + if (ObjectUtil.isNotEmpty(body)) { + dataToSign.append(body); + } + HMac hMac = new HMac(HmacAlgorithm.HmacSHA1, properties.getApiSecret().getBytes(StandardCharsets.UTF_8)); + byte[] signData = hMac.digest(dataToSign.toString().getBytes(StandardCharsets.UTF_8)); + String encodedSignature = Base64.getEncoder().encodeToString(signData); + + return "Qiniu " + properties.getApiKey() + ":" + encodedSignature; + } + + @Override + public List parseSmsReceiveStatus(String text) { + + JSONObject status = JSONUtil.parseObj(text); + //字段参考 https://developer.qiniu.com/sms/5910/message-push + return ListUtil.of(new SmsReceiveRespDTO() + .setSuccess("DELIVRD".equals(status.getJSONArray("items").getJSONObject(0).getStr("status"))) // 是否接收成功 + .setErrorMsg(status.getJSONArray("items").getJSONObject(0).getStr("status")) + .setMobile(status.getJSONArray("items").getJSONObject(0).getStr("mobile")) // 手机号 + .setReceiveTime(LocalDateTimeUtil.of(status.getJSONArray("items").getJSONObject(0).getLong("delivrd_at")*1000L)) + .setSerialNo(status.getJSONArray("items").getJSONObject(0).getStr("message_id")) // 发送序列号 + .setLogId(Long.valueOf(status.getJSONArray("items").getJSONObject(0).getStr("seq")))); // logId + } + + @Override + public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable { + // 1. 执行请求 + // 参考链接 https://developer.qiniu.com/sms/5969/query-a-single-template + HashMap queryParam = new HashMap<>(); + queryParam.put("template_id", apiTemplateId); + JSONObject response = request("GET", null, queryParam); + + // 2.1 请求失败 + String status = response.getStr("audit_status"); + if (!Objects.equals(status, "passed")) { + log.error("[getSmsTemplate][模版编号({}) 响应不正确({})]", apiTemplateId, response); + return null; + } + // 2.2 请求成功 + return new SmsTemplateRespDTO() + .setId(response.getStr("id")) + .setContent(response.getStr("template")) + .setAuditStatus(convertSmsTemplateAuditStatus(response.getStr("audit_status"))) + .setAuditReason(response.getStr("reject_reason")); + } + + @VisibleForTesting + Integer convertSmsTemplateAuditStatus(String templateStatus) { + + if(Objects.equals(templateStatus, "passed")){ + return SmsTemplateAuditStatusEnum.SUCCESS.getStatus(); + }else { + throw new IllegalArgumentException(String.format("未知审核状态(%str)", templateStatus)); + } + } +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/enums/SmsChannelEnum.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/enums/SmsChannelEnum.java index 88f578a18..cbbde696b 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/enums/SmsChannelEnum.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/enums/SmsChannelEnum.java @@ -18,6 +18,7 @@ public enum SmsChannelEnum { ALIYUN("ALIYUN", "阿里云"), TENCENT("TENCENT", "腾讯云"), HUAWEI("HUAWEI", "华为云"), + QINIU("QINIU", "七牛云"), ; /** @@ -34,3 +35,4 @@ public enum SmsChannelEnum { } } + diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClientTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClientTest.java new file mode 100644 index 000000000..c64c39470 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClientTest.java @@ -0,0 +1,128 @@ +package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; + +import cn.iocoder.yudao.framework.common.core.KeyValue; +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; +import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; +import com.google.common.collect.Lists; + +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.MockedStatic; + +import java.time.LocalDateTime; +import java.util.List; + +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mockStatic; + +/** + * {@link QiniuSmsClient} 的单元测试 + * + * @author scholar + */ +public class QiniuSmsClientTest extends BaseMockitoUnitTest { + + private final SmsChannelProperties properties = new SmsChannelProperties() + .setApiKey(randomString())// 随机一个 apiKey,避免构建报错 + .setApiSecret(randomString()) // 随机一个 apiSecret,避免构建报错 + .setSignature("芋道源码"); + + @InjectMocks + private QiniuSmsClient smsClient = new QiniuSmsClient(properties); + + @Test + public void testDoInit() { + // 调用 + smsClient.doInit(); + } + + @Test + public void testDoSendSms_success() throws Throwable { + + try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { + // 准备参数 + Long sendLogId = randomLongId(); + String mobile = randomString(); + String apiTemplateId = randomString() + " " + randomString(); + List> templateParams = Lists.newArrayList( + new KeyValue<>("1", 1234), new KeyValue<>("2", "login")); + // mock 方法 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) + .thenReturn( + "{\"message_id\":\"17245678901\"}" + ); + // 调用 + SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, + apiTemplateId, templateParams); + // 断言 + assertTrue(result.getSuccess()); + assertEquals("17245678901", result.getSerialNo()); + } + } + + @Test + public void testDoSendSms_fail() throws Throwable { + try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { + // 准备参数 + Long sendLogId = randomLongId(); + String mobile = randomString(); + String apiTemplateId = randomString() + " " + randomString(); + List> templateParams = Lists.newArrayList( + new KeyValue<>("1", 1234), new KeyValue<>("2", "login")); + + // mock 方法 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) + .thenReturn( + "{\"error\":\"BadToken\",\"message\":\"Your authorization token is invalid\",\"request_id\":\"etziWcJFo1C8Ne8X\"}" + ); + // 调用 + SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, + apiTemplateId, templateParams); + // 断言 + assertFalse(result.getSuccess()); + } + } + + @Test + public void testGetSmsTemplate() throws Throwable { + try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { + // 准备参数 + String apiTemplateId = randomString(); + // mock 方法 + httpUtilsMockedStatic.when(() -> HttpUtils.get(anyString(), anyMap())) + .thenReturn("{\"audit_status\":\"passed\",\"created_at\":1724231187,\"description\":\"\",\"disable_broadcast\":false,\"disable_broadcast_reason\":\"\",\"disable_reason\":\"\",\"disabled\":false,\"id\":\"1826184073773596672\",\"is_oversea\":false,\"name\":\"dd\",\"parameters\":[\"code\"],\"reject_reason\":\"\",\"signature_id\":\"1826099896017498112\",\"signature_text\":\"yudao\",\"template\":\"您的验证码为:${code}\",\"type\":\"verification\",\"uid\":1383022432,\"updated_at\":1724288561,\"variable_count\":0}"); + // 调用 + SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId); + // 断言 + assertEquals("1826184073773596672", result.getId()); + assertEquals("您的验证码为:${code}", result.getContent()); + assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus()); + assertEquals("", result.getAuditReason()); + } + } + + @Test + public void testParseSmsReceiveStatus() { + // 准备参数 + String text = "{\"items\":[{\"mobile\":\"18881234567\",\"message_id\":\"10135515063508004167\",\"status\":\"DELIVRD\",\"delivrd_at\":1724591666,\"error\":\"DELIVRD\",\"seq\":\"123\"}]}"; + // 调用 + List statuses = smsClient.parseSmsReceiveStatus(text); + // 断言 + assertEquals(1, statuses.size()); + assertTrue(statuses.getFirst().getSuccess()); + assertEquals("DELIVRD", statuses.getFirst().getErrorMsg()); + assertEquals(LocalDateTime.of(2024, 8, 25, 21, 14, 26), statuses.getFirst().getReceiveTime()); + assertEquals("18881234567", statuses.getFirst().getMobile()); + assertEquals("10135515063508004167", statuses.getFirst().getSerialNo()); + assertEquals(123, statuses.getFirst().getLogId()); + } + +} \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java index f1db141e8..3752e5763 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java @@ -1,7 +1,7 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; import cn.iocoder.yudao.framework.common.core.KeyValue; -import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient; import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; @@ -11,39 +11,19 @@ import org.junit.jupiter.api.Test; import java.util.List; /** - * 各种 {@link SmsClientTests 集成测试 + * 各种 {@link SmsClient} 的集成测试 * * @author 芋道源码 */ public class SmsClientTests { - @Test - @Disabled - public void testHuaweiSmsClient_sendSms() throws Throwable { - SmsChannelProperties properties = new SmsChannelProperties() - .setApiKey("123") - .setApiSecret("456") - .setSignature("runpu"); - HuaweiSmsClient client = new HuaweiSmsClient(properties); - // 准备参数 - Long sendLogId = System.currentTimeMillis(); - String mobile = "15601691323"; - String apiTemplateId = "xx test01"; - List> templateParams = List.of(new KeyValue<>("code", "1024")); - // 调用 - SmsSendRespDTO smsSendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, templateParams); - // 打印结果 - System.out.println(smsSendRespDTO); - } - // ========== 阿里云 ========== - @Test @Disabled public void testAliyunSmsClient_getSmsTemplate() throws Throwable { SmsChannelProperties properties = new SmsChannelProperties() - .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR") - .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz"); + .setApiKey(System.getenv("SMS_ALIYUN_ACCESS_KEY")) + .setApiSecret(System.getenv("SMS_ALIYUN_SECRET_KEY")); AliyunSmsClient client = new AliyunSmsClient(properties); // 准备参数 String apiTemplateId = "SMS_207945135"; @@ -57,9 +37,9 @@ public class SmsClientTests { @Disabled public void testAliyunSmsClient_sendSms() throws Throwable { SmsChannelProperties properties = new SmsChannelProperties() - .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR") - .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz") - .setSignature("runpu"); + .setApiKey(System.getenv("SMS_ALIYUN_ACCESS_KEY")) + .setApiSecret(System.getenv("SMS_ALIYUN_SECRET_KEY")) + .setSignature("Ballcat"); AliyunSmsClient client = new AliyunSmsClient(properties); // 准备参数 Long sendLogId = System.currentTimeMillis(); @@ -71,49 +51,21 @@ public class SmsClientTests { System.out.println(sendRespDTO); } - @Test - @Disabled - public void testAliyunSmsClient_parseSmsReceiveStatus() { - SmsChannelProperties properties = new SmsChannelProperties() - .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR") - .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz"); - AliyunSmsClient client = new AliyunSmsClient(properties); - // 准备参数 - String text = "[\n" + - " {\n" + - " \"phone_number\" : \"13900000001\",\n" + - " \"send_time\" : \"2017-01-01 11:12:13\",\n" + - " \"report_time\" : \"2017-02-02 22:23:24\",\n" + - " \"success\" : true,\n" + - " \"err_code\" : \"DELIVERED\",\n" + - " \"err_msg\" : \"用户接收成功\",\n" + - " \"sms_size\" : \"1\",\n" + - " \"biz_id\" : \"12345\",\n" + - " \"out_id\" : \"67890\"\n" + - " }\n" + - "]"; - // mock 方法 - - // 调用 - List statuses = client.parseSmsReceiveStatus(text); - // 打印结果 - System.out.println(statuses); - } - // ========== 腾讯云 ========== @Test @Disabled public void testTencentSmsClient_sendSms() throws Throwable { + String sdkAppId = "1400500458"; SmsChannelProperties properties = new SmsChannelProperties() - .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR 1428926523") - .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz") + .setApiKey(System.getenv("SMS_TENCENT_ACCESS_KEY") + " " + sdkAppId) + .setApiSecret(System.getenv("SMS_TENCENT_SECRET_KEY")) .setSignature("芋道源码"); TencentSmsClient client = new TencentSmsClient(properties); // 准备参数 Long sendLogId = System.currentTimeMillis(); String mobile = "15601691323"; - String apiTemplateId = "2136358"; + String apiTemplateId = "358212"; // 调用 SmsSendRespDTO sendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, List.of(new KeyValue<>("code", "1024"))); // 打印结果 @@ -123,17 +75,77 @@ public class SmsClientTests { @Test @Disabled public void testTencentSmsClient_getSmsTemplate() throws Throwable { + String sdkAppId = "1400500458"; SmsChannelProperties properties = new SmsChannelProperties() - .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR 1428926523") - .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz") + .setApiKey(System.getenv("SMS_TENCENT_ACCESS_KEY") + " " + sdkAppId) + .setApiSecret(System.getenv("SMS_TENCENT_SECRET_KEY")) .setSignature("芋道源码"); TencentSmsClient client = new TencentSmsClient(properties); // 准备参数 - String apiTemplateId = "2136358"; + String apiTemplateId = "358212"; // 调用 SmsTemplateRespDTO template = client.getSmsTemplate(apiTemplateId); // 打印结果 System.out.println(template); } -} + // ========== 华为云 ========== + + @Test + @Disabled + public void testHuaweiSmsClient_sendSms() throws Throwable { + String sender = "x8824060312575"; + SmsChannelProperties properties = new SmsChannelProperties() + .setApiKey(System.getenv("SMS_HUAWEI_ACCESS_KEY") + " " + sender) + .setApiSecret(System.getenv("SMS_HUAWEI_SECRET_KEY")) + .setSignature("runpu"); + HuaweiSmsClient client = new HuaweiSmsClient(properties); + // 准备参数 + Long sendLogId = System.currentTimeMillis(); + String mobile = "17321315478"; + String apiTemplateId = "3644cdab863546a3b718d488659a99ef"; + List> templateParams = List.of(new KeyValue<>("code", "1024")); + // 调用 + SmsSendRespDTO smsSendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, templateParams); + // 打印结果 + System.out.println(smsSendRespDTO); + } + + // ========== 七牛云 ========== + + @Test + @Disabled + public void testQiniuSmsClient_sendSms() throws Throwable { + + SmsChannelProperties properties = new SmsChannelProperties() + .setApiKey("SMS_QINIU_ACCESS_KEY") + .setApiSecret("SMS_QINIU_SECRET_KEY"); + QiniuSmsClient client = new QiniuSmsClient(properties); + // 准备参数 + Long sendLogId = System.currentTimeMillis(); + String mobile = "17321315478"; + String apiTemplateId = "3644cdab863546a3b718d488659a99ef"; + List> templateParams = List.of(new KeyValue<>("code", "1122")); + // 调用 + SmsSendRespDTO smsSendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, templateParams); + // 打印结果 + System.out.println(smsSendRespDTO); + } + + @Test + @Disabled + public void testQiniuSmsClient_getSmsTemplate() throws Throwable { + + SmsChannelProperties properties = new SmsChannelProperties() + .setApiKey("SMS_QINIU_ACCESS_KEY") + .setApiSecret("SMS_QINIU_SECRET_KEY"); + QiniuSmsClient client = new QiniuSmsClient(properties); + // 准备参数 + String apiTemplateId = "3644cdab863546a3b718d488659a99ef"; + // 调用 + SmsTemplateRespDTO template = client.getSmsTemplate(apiTemplateId); + // 打印结果 + System.out.println(template); + } + +} \ No newline at end of file From 1e4cc953d342a61b848bf2f529201b98125c9a15 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 26 Aug 2024 22:13:16 +0800 Subject: [PATCH 19/42] =?UTF-8?q?=E3=80=90BUG=E3=80=91=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E9=99=90=E6=97=B6=E6=8A=98=E6=89=A3=E6=9B=B4=E6=96=B0=E6=97=B6?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=E8=AE=BE=E7=BD=AE=E6=B4=BB=E5=8A=A8=E5=BC=80?= =?UTF-8?q?=E5=A7=8B=E6=97=B6=E9=97=B4=E7=82=B9=E5=92=8C=E6=B4=BB=E5=8A=A8?= =?UTF-8?q?=E7=BB=93=E6=9D=9F=E6=97=B6=E9=97=B4=E7=82=B9=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/discount/DiscountActivityServiceImpl.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountActivityServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountActivityServiceImpl.java index 25872d8e8..0c995267b 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountActivityServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountActivityServiceImpl.java @@ -104,7 +104,10 @@ public class DiscountActivityServiceImpl implements DiscountActivityService { } // 计算新增的记录 List newDiscountProducts = convertList(updateReqVO.getProducts(), - product -> DiscountActivityConvert.INSTANCE.convert(product).setActivityId(updateReqVO.getId())); + product -> DiscountActivityConvert.INSTANCE.convert(product) + .setActivityId(updateReqVO.getId()) + .setActivityStartTime(updateReqVO.getStartTime()) + .setActivityEndTime(updateReqVO.getEndTime())); newDiscountProducts.removeIf(product -> dbDiscountProducts.stream().anyMatch( dbProduct -> DiscountActivityConvert.INSTANCE.isEquals(dbProduct, product))); // 如果匹配到,说明是更新的 if (CollectionUtil.isNotEmpty(newDiscountProducts)) { From 710f29d911f4310b78d4666fe4631f9879f3f263 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Tue, 27 Aug 2024 16:46:04 +0800 Subject: [PATCH 20/42] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E5=95=86=E5=9F=8E:=20=E6=BB=A1=E5=87=8F?= =?UTF-8?q?=E9=80=81=E6=B4=BB=E5=8A=A8=20CRUD=20=E9=83=A8=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/RewardActivityMatchRespDTO.java | 48 ++++++- .../promotion/enums/ErrorCodeConstants.java | 3 +- .../common/PromotionProductScopeEnum.java | 13 ++ .../app/activity/AppActivityController.java | 74 +++++++--- .../dataobject/reward/RewardActivityDO.java | 4 +- .../mysql/reward/RewardActivityMapper.java | 12 +- .../service/reward/RewardActivityService.java | 3 +- .../reward/RewardActivityServiceImpl.java | 100 +++++--------- .../reward/RewardActivityServiceImplTest.java | 128 +++++++++--------- .../TradeRewardActivityPriceCalculator.java | 4 +- ...radeRewardActivityPriceCalculatorTest.java | 6 +- 11 files changed, 220 insertions(+), 175 deletions(-) diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java index 6ae71a1d9..a174637af 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java @@ -1,8 +1,12 @@ package cn.iocoder.yudao.module.promotion.api.reward.dto; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum; +import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; import lombok.Data; +import java.io.Serializable; +import java.time.LocalDateTime; import java.util.List; /** @@ -21,28 +25,50 @@ public class RewardActivityMatchRespDTO { * 活动标题 */ private String name; + /** + * 状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + /** + * 开始时间 + */ + private LocalDateTime startTime; + /** + * 结束时间 + */ + private LocalDateTime endTime; + /** + * 备注 + */ + private String remark; /** * 条件类型 * * 枚举 {@link PromotionConditionTypeEnum} */ private Integer conditionType; + /** + * 商品范围 + * + * 枚举 {@link PromotionProductScopeEnum} + */ + private Integer productScope; + /** + * 商品 SPU 编号的数组 + */ + private List productScopeValues; /** * 优惠规则的数组 */ private List rules; - /** - * 商品 SPU 编号的数组 - */ - private List spuIds; - - // TODO 芋艿:后面 RewardActivityRespDTO 有了之后,Rule 可以放过去 /** * 优惠规则 */ @Data - public static class Rule { + public static class Rule implements Serializable { /** * 优惠门槛 @@ -59,10 +85,18 @@ public class RewardActivityMatchRespDTO { * 是否包邮 */ private Boolean freeDelivery; + /** + * 是否赠送积分 + */ + private Boolean givePoint; /** * 赠送的积分 */ private Integer point; + /** + * 是否赠送优惠券 + */ + private Boolean giveCoupon; /** * 赠送的优惠劵编号的数组 */ diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java index 8cebd6e13..e1efb9c91 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java @@ -44,7 +44,8 @@ public interface ErrorCodeConstants { ErrorCode REWARD_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_006_002, "满减送活动已关闭,不能修改"); ErrorCode REWARD_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED = new ErrorCode(1_013_006_003, "满减送活动未关闭,不能删除"); ErrorCode REWARD_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_006_004, "满减送活动已关闭,不能重复关闭"); - ErrorCode REWARD_ACTIVITY_CLOSE_FAIL_STATUS_END = new ErrorCode(1_013_006_005, "满减送活动已结束,不能关闭"); + ErrorCode REWARD_ACTIVITY_SCOPE_ALL_EXISTS = new ErrorCode(1_013_006_005, "已存在商品范围为全场的满减送活动"); + ErrorCode REWARD_ACTIVITY_SCOPE_CATEGORY_EXISTS = new ErrorCode(1_013_006_006, "存在商品类型参加了其它满减送活动"); // ========== TODO 空着 1-013-007-000 ============ diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/common/PromotionProductScopeEnum.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/common/PromotionProductScopeEnum.java index 98e2ac7c9..c082e190f 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/common/PromotionProductScopeEnum.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/common/PromotionProductScopeEnum.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.promotion.enums.common; +import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.common.core.IntArrayValuable; import lombok.AllArgsConstructor; import lombok.Getter; @@ -35,4 +36,16 @@ public enum PromotionProductScopeEnum implements IntArrayValuable { return ARRAYS; } + public static boolean isAll(Integer scope) { + return ObjUtil.equal(scope, ALL.scope); + } + + public static boolean isSpu(Integer scope) { + return ObjUtil.equal(scope, SPU.scope); + } + + public static boolean isCategory(Integer scope) { + return ObjUtil.equal(scope, CATEGORY.scope); + } + } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/AppActivityController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/AppActivityController.java index a59ff7df1..fe15c0f71 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/AppActivityController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/AppActivityController.java @@ -2,14 +2,19 @@ package cn.iocoder.yudao.module.promotion.controller.app.activity; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; +import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; import cn.iocoder.yudao.module.promotion.controller.app.activity.vo.AppActivityRespVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO; import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO; import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountActivityDO; import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO; +import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO; import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO; +import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum; import cn.iocoder.yudao.module.promotion.service.bargain.BargainActivityService; import cn.iocoder.yudao.module.promotion.service.combination.CombinationActivityService; @@ -48,6 +53,8 @@ public class AppActivityController { private DiscountActivityService discountActivityService; @Resource private RewardActivityService rewardActivityService; + @Resource + private ProductSpuApi productSpuApi; @GetMapping("/list-by-spu-id") @Operation(summary = "获得单个商品,近期参与的每个活动") @@ -141,29 +148,52 @@ public class AppActivityController { item.getName(), productMap.get(item.getId()), item.getStartTime(), item.getEndTime()))); } + private static void buildAppActivityRespVO(RewardActivityDO rewardActivity, Collection spuIds, + List activityList) { + for (Long spuId : spuIds) { + // 校验商品是否已经加入过活动 + if (anyMatch(activityList, appActivity -> ObjUtil.equal(appActivity.getId(), rewardActivity.getId()) && + ObjUtil.equal(appActivity.getSpuId(), spuId))) { + continue; + } + activityList.add(new AppActivityRespVO(rewardActivity.getId(), + PromotionTypeEnum.REWARD_ACTIVITY.getType(), rewardActivity.getName(), spuId, + rewardActivity.getStartTime(), rewardActivity.getEndTime())); + } + } + private void getRewardActivities(Collection spuIds, LocalDateTime now, List activityList) { - // TODO @puhui999:有 3 范围,不只 spuId,还有 categoryId,全部,下次 fix - //List rewardActivityList = rewardActivityService.getRewardActivityBySpuIdsAndStatusAndDateTimeLt( - // spuIds, PromotionActivityStatusEnum.RUN.getStatus(), now); - //if (CollUtil.isEmpty(rewardActivityList)) { - // return; - //} - // - //Map> spuIdAndActivityMap = spuIds.stream() - // .collect(Collectors.toMap( - // spuId -> spuId, - // spuId -> rewardActivityList.stream() - // .filter(activity -> activity.getProductSpuIds().contains(spuId)) - // .max(Comparator.comparing(RewardActivityDO::getCreateTime)))); - //for (Long supId : spuIdAndActivityMap.keySet()) { - // if (spuIdAndActivityMap.get(supId).isEmpty()) { - // continue; - // } - // - // RewardActivityDO rewardActivityDO = spuIdAndActivityMap.get(supId).get(); - // activityList.add(new AppActivityRespVO(rewardActivityDO.getId(), PromotionTypeEnum.REWARD_ACTIVITY.getType(), - // rewardActivityDO.getName(), supId, rewardActivityDO.getStartTime(), rewardActivityDO.getEndTime())); - //} + // 1.1 获得所有的活动 + List rewardActivityList = rewardActivityService.getRewardActivityByStatusAndDateTimeLt( + CommonStatusEnum.ENABLE.getStatus(), now); + if (CollUtil.isEmpty(rewardActivityList)) { + return; + } + // 1.2 获得所有的商品信息 + List spuList = productSpuApi.getSpuList(spuIds); + if (CollUtil.isEmpty(spuList)) { + return; + } + + // 2. 构建活动 + for (RewardActivityDO rewardActivity : rewardActivityList) { + // 情况一:所有商品都能参加 + if (PromotionProductScopeEnum.isAll(rewardActivity.getProductScope())) { + buildAppActivityRespVO(rewardActivity, spuIds, activityList); + } + // 情况二:指定商品参加 + if (PromotionProductScopeEnum.isSpu(rewardActivity.getProductScope())) { + List fSpuIds = spuList.stream().map(ProductSpuRespDTO::getId).filter(id -> + rewardActivity.getProductScopeValues().contains(id)).toList(); + buildAppActivityRespVO(rewardActivity, fSpuIds, activityList); + } + // 情况三:指定商品类型参加 + if (PromotionProductScopeEnum.isCategory(rewardActivity.getProductScope())) { + List fSpuIds = spuList.stream().filter(spuItem -> rewardActivity.getProductScopeValues() + .contains(spuItem.getCategoryId())).map(ProductSpuRespDTO::getId).toList(); + buildAppActivityRespVO(rewardActivity, fSpuIds, activityList); + } + } } } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java index 98d3e8d81..9a7135063 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java @@ -1,8 +1,8 @@ package cn.iocoder.yudao.module.promotion.dal.dataobject.reward; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler; -import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum; import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum; import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; import com.baomidou.mybatisplus.annotation.KeySequence; @@ -40,7 +40,7 @@ public class RewardActivityDO extends BaseDO { /** * 状态 * - * 枚举 {@link PromotionActivityStatusEnum} + * 枚举 {@link CommonStatusEnum} */ private Integer status; /** diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/reward/RewardActivityMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/reward/RewardActivityMapper.java index ca9e9668f..915696967 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/reward/RewardActivityMapper.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/reward/RewardActivityMapper.java @@ -30,10 +30,6 @@ public interface RewardActivityMapper extends BaseMapperX { .orderByDesc(RewardActivityDO::getId)); } - default List selectListByStatus(Collection statuses) { - return selectList(RewardActivityDO::getStatus, statuses); - } - default List selectListByProductScopeAndStatus(Integer productScope, Integer status) { return selectList(new LambdaQueryWrapperX() .eq(RewardActivityDO::getProductScope, productScope) @@ -53,16 +49,16 @@ public interface RewardActivityMapper extends BaseMapperX { * 获取指定活动编号的活动列表且 * 开始时间和结束时间小于给定时间 dateTime 的活动列表 * - * @param ids 活动编号 + * @param status 状态 * @param dateTime 指定日期 * @return 活动列表 */ - default List selectListByIdsAndDateTimeLt(Collection ids, LocalDateTime dateTime) { + default List selectListByStatusAndDateTimeLt(Integer status, LocalDateTime dateTime) { return selectList(new LambdaQueryWrapperX() - .in(RewardActivityDO::getId, ids) + .eq(RewardActivityDO::getStatus, status) .lt(RewardActivityDO::getStartTime, dateTime) .gt(RewardActivityDO::getEndTime, dateTime)// 开始时间 < 指定时间 < 结束时间,也就是说获取指定时间段的活动 - .orderByDesc(RewardActivityDO::getCreateTime) + .orderByAsc(RewardActivityDO::getStartTime) ); } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityService.java index e2e225608..1d4b978e9 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityService.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityService.java @@ -75,11 +75,10 @@ public interface RewardActivityService { /** * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录 * - * @param spuIds spu 编号 * @param status 状态 * @param dateTime 当前日期时间 * @return 满减送活动列表 */ - List getRewardActivityBySpuIdsAndStatusAndDateTimeLt(Collection spuIds, Integer status, LocalDateTime dateTime); + List getRewardActivityByStatusAndDateTimeLt(Integer status, LocalDateTime dateTime); } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java index 98fa990c1..a6a865eab 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java @@ -1,17 +1,18 @@ package cn.iocoder.yudao.module.promotion.service.reward; -import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.product.api.category.ProductCategoryApi; import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO; +import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityBaseVO; import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityCreateReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityUpdateReqVO; import cn.iocoder.yudao.module.promotion.convert.reward.RewardActivityConvert; import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO; import cn.iocoder.yudao.module.promotion.dal.mysql.reward.RewardActivityMapper; -import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum; import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; import cn.iocoder.yudao.module.promotion.util.PromotionUtils; import jakarta.annotation.Resource; @@ -20,14 +21,13 @@ import org.springframework.validation.annotation.Validated; import java.time.LocalDateTime; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Objects; +import static cn.hutool.core.collection.CollUtil.intersectionDistinct; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch; import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*; -import static java.util.Arrays.asList; /** * 满减送活动 Service 实现类 @@ -51,7 +51,7 @@ public class RewardActivityServiceImpl implements RewardActivityService { // 1.1 校验商品范围 validateProductScope(createReqVO.getProductScope(), createReqVO.getProductScopeValues()); // 1.2 校验商品是否冲突 - //validateRewardActivitySpuConflicts(null, createReqVO.getProductSpuIds()); + validateRewardActivitySpuConflicts(null, createReqVO); // 2. 插入 RewardActivityDO rewardActivity = RewardActivityConvert.INSTANCE.convert(createReqVO) @@ -65,13 +65,13 @@ public class RewardActivityServiceImpl implements RewardActivityService { public void updateRewardActivity(RewardActivityUpdateReqVO updateReqVO) { // 1.1 校验存在 RewardActivityDO dbRewardActivity = validateRewardActivityExists(updateReqVO.getId()); - if (dbRewardActivity.getStatus().equals(PromotionActivityStatusEnum.CLOSE.getStatus())) { // 已关闭的活动,不能修改噢 + if (dbRewardActivity.getStatus().equals(CommonStatusEnum.DISABLE.getStatus())) { // 已关闭的活动,不能修改噢 throw exception(REWARD_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED); } // 1.2 校验商品范围 validateProductScope(updateReqVO.getProductScope(), updateReqVO.getProductScopeValues()); // 1.3 校验商品是否冲突 - //validateRewardActivitySpuConflicts(updateReqVO.getId(), updateReqVO.getProductSpuIds()); + validateRewardActivitySpuConflicts(updateReqVO.getId(), updateReqVO); // 2. 更新 RewardActivityDO updateObj = RewardActivityConvert.INSTANCE.convert(updateReqVO) @@ -82,17 +82,13 @@ public class RewardActivityServiceImpl implements RewardActivityService { @Override public void closeRewardActivity(Long id) { // 校验存在 - // TODO @puhui999:去掉 PromotionActivityStatusEnum,使用 CommonStatus 作为状态哈。开启,关闭 RewardActivityDO dbRewardActivity = validateRewardActivityExists(id); - if (dbRewardActivity.getStatus().equals(PromotionActivityStatusEnum.CLOSE.getStatus())) { // 已关闭的活动,不能关闭噢 + if (dbRewardActivity.getStatus().equals(CommonStatusEnum.DISABLE.getStatus())) { // 已关闭的活动,不能关闭噢 throw exception(REWARD_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED); } - if (dbRewardActivity.getStatus().equals(PromotionActivityStatusEnum.END.getStatus())) { // 已关闭的活动,不能关闭噢 - throw exception(REWARD_ACTIVITY_CLOSE_FAIL_STATUS_END); - } // 更新 - RewardActivityDO updateObj = new RewardActivityDO().setId(id).setStatus(PromotionActivityStatusEnum.CLOSE.getStatus()); + RewardActivityDO updateObj = new RewardActivityDO().setId(id).setStatus(CommonStatusEnum.DISABLE.getStatus()); rewardActivityMapper.updateById(updateObj); } @@ -100,7 +96,7 @@ public class RewardActivityServiceImpl implements RewardActivityService { public void deleteRewardActivity(Long id) { // 校验存在 RewardActivityDO dbRewardActivity = validateRewardActivityExists(id); - if (!dbRewardActivity.getStatus().equals(PromotionActivityStatusEnum.CLOSE.getStatus())) { // 未关闭的活动,不能删除噢 + if (dbRewardActivity.getStatus().equals(CommonStatusEnum.ENABLE.getStatus())) { // 未关闭的活动,不能删除噢 throw exception(REWARD_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED); } @@ -116,27 +112,30 @@ public class RewardActivityServiceImpl implements RewardActivityService { return activity; } - // TODO @芋艿:逻辑有问题,需要优化;要分成全场、和指定来校验; - // TODO @puhui999: 下次提交 fix /** * 校验商品参加的活动是否冲突 * - * @param id 活动编号 - * @param spuIds 商品 SPU 编号数组 + * @param id 活动编号 + * @param rewardActivity 请求 */ - private void validateRewardActivitySpuConflicts(Long id, Collection spuIds) { - if (CollUtil.isEmpty(spuIds)) { - return; - } - // 查询商品参加的活动 - List rewardActivityList = getRewardActivityListBySpuIds(spuIds, - asList(PromotionActivityStatusEnum.WAIT.getStatus(), PromotionActivityStatusEnum.RUN.getStatus())); + private void validateRewardActivitySpuConflicts(Long id, RewardActivityBaseVO rewardActivity) { + List list = rewardActivityMapper.selectList(RewardActivityDO::getProductScope, + rewardActivity.getProductScope(), RewardActivityDO::getStatus, CommonStatusEnum.ENABLE.getStatus()); if (id != null) { // 排除自己这个活动 - rewardActivityList.removeIf(activity -> id.equals(activity.getId())); + list.removeIf(activity -> id.equals(activity.getId())); } - // 如果非空,则说明冲突 - if (CollUtil.isNotEmpty(rewardActivityList)) { - throw exception(REWARD_ACTIVITY_SPU_CONFLICTS); + + // 情况一:全部商品参加 + if (PromotionProductScopeEnum.isAll(rewardActivity.getProductScope()) && !list.isEmpty()) { + throw exception(REWARD_ACTIVITY_SCOPE_ALL_EXISTS); + } + if (PromotionProductScopeEnum.isSpu(rewardActivity.getProductScope()) || // 情况二:指定商品参加 + PromotionProductScopeEnum.isCategory(rewardActivity.getProductScope())) { // 情况三:指定商品类型参加 + if (anyMatch(list, item -> !intersectionDistinct(item.getProductScopeValues(), + rewardActivity.getProductScopeValues()).isEmpty())) { + throw exception(PromotionProductScopeEnum.isSpu(rewardActivity.getProductScope()) ? + REWARD_ACTIVITY_SPU_CONFLICTS : REWARD_ACTIVITY_SCOPE_CATEGORY_EXISTS); + } } } @@ -148,21 +147,6 @@ public class RewardActivityServiceImpl implements RewardActivityService { } } - /** - * 获得商品参加的满减送活动的数组 - * - * @param spuIds 商品 SPU 编号数组 - * @param statuses 活动状态数组 - * @return 商品参加的满减送活动的数组 - */ - private List getRewardActivityListBySpuIds(Collection spuIds, - Collection statuses) { - // TODO @puhui999: 下次 fix - //List list = rewardActivityMapper.selectListByStatus(statuses); - //return CollUtil.filter(list, activity -> CollUtil.containsAny(activity.getProductSpuIds(), spuIds)); - return List.of(); - } - @Override public RewardActivityDO getRewardActivity(Long id) { return rewardActivityMapper.selectById(id); @@ -176,31 +160,13 @@ public class RewardActivityServiceImpl implements RewardActivityService { @Override public List getMatchRewardActivityList(Collection spuIds) { // TODO 芋艿:待实现;先指定,然后再全局的; -// // 如果有全局活动,则直接选择它 -// List allActivities = rewardActivityMapper.selectListByProductScopeAndStatus( -// PromotionProductScopeEnum.ALL.getScope(), PromotionActivityStatusEnum.RUN.getStatus()); -// if (CollUtil.isNotEmpty(allActivities)) { -// return MapUtil.builder(allActivities.get(0), spuIds).build(); -// } -// -// // 查询某个活动参加的活动 -// List productActivityList = getRewardActivityListBySpuIds(spuIds, -// singleton(PromotionActivityStatusEnum.RUN.getStatus())); -// return convertMap(productActivityList, activity -> activity, -// rewardActivityDO -> intersectionDistinct(rewardActivityDO.getProductSpuIds(), spuIds)); // 求交集返回 - return null; + List list = rewardActivityMapper.selectListBySpuIdsAndStatus(spuIds, CommonStatusEnum.ENABLE.getStatus()); + return BeanUtils.toBean(list, RewardActivityMatchRespDTO.class); } @Override - public List getRewardActivityBySpuIdsAndStatusAndDateTimeLt(Collection spuIds, Integer status, LocalDateTime dateTime) { - // 1. 查询出指定 spuId 的 spu 参加的活动 - List rewardActivityList = rewardActivityMapper.selectListBySpuIdsAndStatus(spuIds, status); - if (CollUtil.isEmpty(rewardActivityList)) { - return Collections.emptyList(); - } - - // 2. 查询活动详情 - return rewardActivityMapper.selectListByIdsAndDateTimeLt(convertSet(rewardActivityList, RewardActivityDO::getId), dateTime); + public List getRewardActivityByStatusAndDateTimeLt(Integer status, LocalDateTime dateTime) { + return rewardActivityMapper.selectListByStatusAndDateTimeLt(status, dateTime); } } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImplTest.java b/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImplTest.java index ca8d85fa7..7e7cf14db 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImplTest.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImplTest.java @@ -1,21 +1,23 @@ package cn.iocoder.yudao.module.promotion.service.reward; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO; import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityCreateReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityUpdateReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO; import cn.iocoder.yudao.module.promotion.dal.mysql.reward.RewardActivityMapper; -import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum; import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum; import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; +import jakarta.annotation.Resource; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.Import; -import jakarta.annotation.Resource; import java.time.Duration; +import java.util.List; import java.util.Set; import static cn.hutool.core.util.RandomUtil.randomEle; @@ -27,15 +29,15 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServic import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.REWARD_ACTIVITY_NOT_EXISTS; -import static java.util.Arrays.asList; +import static com.google.common.primitives.Longs.asList; import static java.util.Collections.singletonList; import static org.junit.jupiter.api.Assertions.*; /** -* {@link RewardActivityServiceImpl} 的单元测试类 -* -* @author 芋道源码 -*/ + * {@link RewardActivityServiceImpl} 的单元测试类 + * + * @author 芋道源码 + */ @Disabled // TODO 芋艿:后续 fix 补充的单测 @Import(RewardActivityServiceImpl.class) public class RewardActivityServiceImplTest extends BaseDbUnitTest { @@ -63,7 +65,7 @@ public class RewardActivityServiceImplTest extends BaseDbUnitTest { // 校验记录的属性是否正确 RewardActivityDO rewardActivity = rewardActivityMapper.selectById(rewardActivityId); assertPojoEquals(reqVO, rewardActivity, "rules"); - assertEquals(rewardActivity.getStatus(), PromotionActivityStatusEnum.WAIT.getStatus()); + assertEquals(rewardActivity.getStatus(), CommonStatusEnum.DISABLE.getStatus()); for (int i = 0; i < reqVO.getRules().size(); i++) { assertPojoEquals(reqVO.getRules().get(i), rewardActivity.getRules().get(i)); } @@ -72,7 +74,7 @@ public class RewardActivityServiceImplTest extends BaseDbUnitTest { @Test public void testUpdateRewardActivity_success() { // mock 数据 - RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.WAIT.getStatus())); + RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())); rewardActivityMapper.insert(dbRewardActivity);// @Sql: 先插入出一条存在的数据 // 准备参数 RewardActivityUpdateReqVO reqVO = randomPojo(RewardActivityUpdateReqVO.class, o -> { @@ -88,7 +90,7 @@ public class RewardActivityServiceImplTest extends BaseDbUnitTest { // 校验是否更新正确 RewardActivityDO rewardActivity = rewardActivityMapper.selectById(reqVO.getId()); // 获取最新的 assertPojoEquals(reqVO, rewardActivity, "rules"); - assertEquals(rewardActivity.getStatus(), PromotionActivityStatusEnum.WAIT.getStatus()); + assertEquals(rewardActivity.getStatus(), CommonStatusEnum.DISABLE.getStatus()); for (int i = 0; i < reqVO.getRules().size(); i++) { assertPojoEquals(reqVO.getRules().get(i), rewardActivity.getRules().get(i)); } @@ -97,7 +99,7 @@ public class RewardActivityServiceImplTest extends BaseDbUnitTest { @Test public void testCloseRewardActivity() { // mock 数据 - RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.WAIT.getStatus())); + RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())); rewardActivityMapper.insert(dbRewardActivity);// @Sql: 先插入出一条存在的数据 // 准备参数 Long id = dbRewardActivity.getId(); @@ -106,7 +108,7 @@ public class RewardActivityServiceImplTest extends BaseDbUnitTest { rewardActivityService.closeRewardActivity(id); // 校验状态 RewardActivityDO rewardActivity = rewardActivityMapper.selectById(id); - assertEquals(rewardActivity.getStatus(), PromotionActivityStatusEnum.CLOSE.getStatus()); + assertEquals(rewardActivity.getStatus(), CommonStatusEnum.DISABLE.getStatus()); } @Test @@ -121,15 +123,15 @@ public class RewardActivityServiceImplTest extends BaseDbUnitTest { @Test public void testDeleteRewardActivity_success() { // mock 数据 - RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.CLOSE.getStatus())); + RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())); rewardActivityMapper.insert(dbRewardActivity);// @Sql: 先插入出一条存在的数据 // 准备参数 Long id = dbRewardActivity.getId(); // 调用 rewardActivityService.deleteRewardActivity(id); - // 校验数据不存在了 - assertNull(rewardActivityMapper.selectById(id)); + // 校验数据不存在了 + assertNull(rewardActivityMapper.selectById(id)); } @Test @@ -143,78 +145,82 @@ public class RewardActivityServiceImplTest extends BaseDbUnitTest { @Test public void testGetRewardActivityPage() { - // mock 数据 - RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> { // 等会查询到 - o.setName("芋艿"); - o.setStatus(PromotionActivityStatusEnum.CLOSE.getStatus()); - }); - rewardActivityMapper.insert(dbRewardActivity); - // 测试 name 不匹配 - rewardActivityMapper.insert(cloneIgnoreId(dbRewardActivity, o -> o.setName("土豆"))); - // 测试 status 不匹配 - rewardActivityMapper.insert(cloneIgnoreId(dbRewardActivity, o -> o.setStatus(PromotionActivityStatusEnum.RUN.getStatus()))); - // 准备参数 - RewardActivityPageReqVO reqVO = new RewardActivityPageReqVO(); - reqVO.setName("芋艿"); - reqVO.setStatus(PromotionActivityStatusEnum.CLOSE.getStatus()); + // mock 数据 + RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> { // 等会查询到 + o.setName("芋艿"); + o.setStatus(CommonStatusEnum.DISABLE.getStatus()); + }); + rewardActivityMapper.insert(dbRewardActivity); + // 测试 name 不匹配 + rewardActivityMapper.insert(cloneIgnoreId(dbRewardActivity, o -> o.setName("土豆"))); + // 测试 status 不匹配 + rewardActivityMapper.insert(cloneIgnoreId(dbRewardActivity, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()))); + // 准备参数 + RewardActivityPageReqVO reqVO = new RewardActivityPageReqVO(); + reqVO.setName("芋艿"); + reqVO.setStatus(CommonStatusEnum.DISABLE.getStatus()); - // 调用 - PageResult pageResult = rewardActivityService.getRewardActivityPage(reqVO); - // 断言 - assertEquals(1, pageResult.getTotal()); - assertEquals(1, pageResult.getList().size()); - assertPojoEquals(dbRewardActivity, pageResult.getList().get(0), "rules"); + // 调用 + PageResult pageResult = rewardActivityService.getRewardActivityPage(reqVO); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbRewardActivity, pageResult.getList().get(0), "rules"); } @Test public void testGetRewardActivities_all() { // mock 数据 - RewardActivityDO allActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.RUN.getStatus()) + RewardActivityDO allActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()) .setProductScope(PromotionProductScopeEnum.ALL.getScope())); rewardActivityMapper.insert(allActivity); - RewardActivityDO productActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.RUN.getStatus()) - .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductSpuIds(asList(1L, 2L))); + RewardActivityDO productActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()) + .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(asList(1L, 2L))); rewardActivityMapper.insert(productActivity); // 准备参数 Set spuIds = asSet(1L, 2L); // 调用 TODO getMatchRewardActivities 没有这个方法,但是找到了 getMatchRewardActivityList - //Map> matchRewardActivities = rewardActivityService.getMatchRewardActivities(spuIds); + List matchRewardActivityList = rewardActivityService.getMatchRewardActivityList(spuIds); // 断言 - //assertEquals(matchRewardActivities.size(), 1); - //Map.Entry> next = matchRewardActivities.entrySet().iterator().next(); - //assertPojoEquals(next.getKey(), allActivity); - //assertEquals(next.getValue(), spuIds); + assertEquals(matchRewardActivityList.size(), 1); + matchRewardActivityList.forEach((activity) -> { + if (activity.getId().equals(productActivity.getId())) { + assertPojoEquals(activity, productActivity); + assertEquals(activity.getProductScopeValues(), asList(1L, 2L)); + } else { + fail(); + } + }); } @Test public void testGetRewardActivities_product() { // mock 数据 - // TODO @puhui999:有单测的问题,也一起瞅瞅 - RewardActivityDO productActivity01 = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.RUN.getStatus()) - .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductSpuIds(asList(1L, 2L))); + RewardActivityDO productActivity01 = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()) + .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(asList(1L, 2L))); rewardActivityMapper.insert(productActivity01); - RewardActivityDO productActivity02 = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.RUN.getStatus()) - .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductSpuIds(singletonList(3L))); + RewardActivityDO productActivity02 = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()) + .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(singletonList(3L))); rewardActivityMapper.insert(productActivity02); // 准备参数 Set spuIds = asSet(1L, 2L, 3L); // 调用 TODO getMatchRewardActivities 没有这个方法,但是找到了 getMatchRewardActivityList - //Map> matchRewardActivities = rewardActivityService.getMatchRewardActivities(spuIds); + List matchRewardActivityList = rewardActivityService.getMatchRewardActivityList(spuIds); // 断言 - //assertEquals(matchRewardActivities.size(), 2); - //matchRewardActivities.forEach((activity, activitySpuIds) -> { - // if (activity.getId().equals(productActivity01.getId())) { - // assertPojoEquals(activity, productActivity01); - // assertEquals(activitySpuIds, asSet(1L, 2L)); - // } else if (activity.getId().equals(productActivity02.getId())) { - // assertPojoEquals(activity, productActivity02); - // assertEquals(activitySpuIds, asSet(3L)); - // } else { - // fail(); - // } - //}); + assertEquals(matchRewardActivityList.size(), 2); + matchRewardActivityList.forEach((activity) -> { + if (activity.getId().equals(productActivity01.getId())) { + assertPojoEquals(activity, productActivity01); + assertEquals(activity.getProductScopeValues(), asList(1L, 2L)); + } else if (activity.getId().equals(productActivity02.getId())) { + assertPojoEquals(activity, productActivity02); + assertEquals(activity.getProductScopeValues(), singletonList(3L)); + } else { + fail(); + } + }); } } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java index 4374783d2..0c25dcb30 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java @@ -10,10 +10,10 @@ import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum; import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; +import jakarta.annotation.Resource; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; -import jakarta.annotation.Resource; import java.util.List; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; @@ -96,7 +96,7 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator private List filterMatchCouponOrderItems(TradePriceCalculateRespBO result, RewardActivityMatchRespDTO rewardActivity) { return filterList(result.getItems(), - orderItem -> CollUtil.contains(rewardActivity.getSpuIds(), orderItem.getSpuId())); + orderItem -> CollUtil.contains(rewardActivity.getProductScopeValues(), orderItem.getSpuId())); } /** diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java index de72ed616..219ae727e 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java @@ -63,10 +63,10 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest // mock 方法(限时折扣 DiscountActivity 信息) when(rewardActivityApi.getMatchRewardActivityList(eq(asSet(1L, 2L, 3L)))).thenReturn(asList( randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(1000L).setName("活动 1000 号") - .setSpuIds(asList(1L, 2L)).setConditionType(PromotionConditionTypeEnum.PRICE.getType()) + .setProductScopeValues(asList(1L, 2L)).setConditionType(PromotionConditionTypeEnum.PRICE.getType()) .setRules(singletonList(new RewardActivityMatchRespDTO.Rule().setLimit(200).setDiscountPrice(70)))), randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(2000L).setName("活动 2000 号") - .setSpuIds(singletonList(3L)).setConditionType(PromotionConditionTypeEnum.COUNT.getType()) + .setProductScopeValues(singletonList(3L)).setConditionType(PromotionConditionTypeEnum.COUNT.getType()) .setRules(asList(new RewardActivityMatchRespDTO.Rule().setLimit(1).setDiscountPrice(10), new RewardActivityMatchRespDTO.Rule().setLimit(2).setDiscountPrice(60), // 最大可满足,因为是 4 个 new RewardActivityMatchRespDTO.Rule().setLimit(10).setDiscountPrice(100)))) @@ -175,7 +175,7 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest // mock 方法(限时折扣 DiscountActivity 信息) when(rewardActivityApi.getMatchRewardActivityList(eq(asSet(1L, 2L)))).thenReturn(singletonList( randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(1000L).setName("活动 1000 号") - .setSpuIds(asList(1L, 2L)).setConditionType(PromotionConditionTypeEnum.PRICE.getType()) + .setProductScopeValues(asList(1L, 2L)).setConditionType(PromotionConditionTypeEnum.PRICE.getType()) .setRules(singletonList(new RewardActivityMatchRespDTO.Rule().setLimit(351).setDiscountPrice(70)))) )); From 63fcc699306f40270ed3ffe048b9b4bcbb3226e6 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Tue, 27 Aug 2024 16:49:02 +0800 Subject: [PATCH 21/42] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E5=95=86=E5=9F=8E:=20=E5=8F=96=E6=B6=88?= =?UTF-8?q?=E6=94=AF=E4=BB=98=E8=AE=A2=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../combination/CombinationRecordServiceImpl.java | 9 +++++++-- .../yudao/module/trade/api/order/TradeOrderApi.java | 8 +++++--- .../trade/enums/order/TradeOrderCancelTypeEnum.java | 3 ++- .../module/trade/api/order/TradeOrderApiImpl.java | 7 ++++--- .../trade/service/order/TradeOrderUpdateService.java | 11 ++++++----- .../service/order/TradeOrderUpdateServiceImpl.java | 8 ++++---- 6 files changed, 28 insertions(+), 18 deletions(-) diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java index cb70b8ea9..c1449d60d 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java @@ -27,6 +27,7 @@ import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStat import cn.iocoder.yudao.module.system.api.social.SocialClientApi; import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO; import cn.iocoder.yudao.module.trade.api.order.TradeOrderApi; +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderCancelTypeEnum; import jakarta.annotation.Nullable; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; @@ -37,7 +38,10 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.time.LocalDateTime; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; @@ -335,7 +339,8 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { List headAndRecords = updateBatchCombinationRecords(headRecord, CombinationRecordStatusEnum.FAILED); // 2. 订单取消 - headAndRecords.forEach(item -> tradeOrderApi.cancelPaidOrder(item.getUserId(), item.getOrderId())); + headAndRecords.forEach(item -> tradeOrderApi.cancelPaidOrder(item.getUserId(), item.getOrderId(), + TradeOrderCancelTypeEnum.COMBINATION_CLOSE)); } /** diff --git a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java index f36f7bc95..d21e88a44 100644 --- a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java +++ b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.trade.api.order; import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO; +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderCancelTypeEnum; import java.util.Collection; import java.util.List; @@ -31,9 +32,10 @@ public interface TradeOrderApi { /** * 取消支付订单 * - * @param userId 用户编号 - * @param orderId 订单编号 + * @param userId 用户编号 + * @param orderId 订单编号 + * @param cancelTypeEnum 取消类型 */ - void cancelPaidOrder(Long userId, Long orderId); + void cancelPaidOrder(Long userId, Long orderId, TradeOrderCancelTypeEnum cancelTypeEnum); } diff --git a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderCancelTypeEnum.java b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderCancelTypeEnum.java index 8ec1e9b16..cfd25468f 100644 --- a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderCancelTypeEnum.java +++ b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderCancelTypeEnum.java @@ -17,7 +17,8 @@ public enum TradeOrderCancelTypeEnum implements IntArrayValuable { PAY_TIMEOUT(10, "超时未支付"), AFTER_SALE_CLOSE(20, "退款关闭"), - MEMBER_CANCEL(30, "买家取消"); + MEMBER_CANCEL(30, "买家取消"), + COMBINATION_CLOSE(40, "拼团关闭"); public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(TradeOrderCancelTypeEnum::getType).toArray(); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApiImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApiImpl.java index 7426585d9..edb675f29 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApiImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApiImpl.java @@ -2,12 +2,13 @@ package cn.iocoder.yudao.module.trade.api.order; import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO; import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert; +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderCancelTypeEnum; import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService; import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService; +import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; -import jakarta.annotation.Resource; import java.util.Collection; import java.util.List; @@ -36,8 +37,8 @@ public class TradeOrderApiImpl implements TradeOrderApi { } @Override - public void cancelPaidOrder(Long userId, Long orderId) { - tradeOrderUpdateService.cancelPaidOrder(userId, orderId); + public void cancelPaidOrder(Long userId, Long orderId, TradeOrderCancelTypeEnum cancelTypeEnum) { + tradeOrderUpdateService.cancelPaidOrder(userId, orderId, cancelTypeEnum); } } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java index e16a08bd7..b38decc17 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.trade.service.order; -import cn.iocoder.yudao.framework.common.enums.TerminalEnum; import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO; import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderRemarkReqVO; import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdateAddressReqVO; @@ -10,7 +9,7 @@ import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettle import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO; import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; - +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderCancelTypeEnum; import jakarta.validation.constraints.NotNull; /** @@ -188,12 +187,14 @@ public interface TradeOrderUpdateService { void updateOrderCombinationInfo(Long orderId, Long activityId, Long combinationRecordId, Long headId); // TODO 芋艿:拼团取消,不调这个接口哈; + /** * 取消支付订单 * - * @param userId 用户编号 - * @param orderId 订单编号 + * @param userId 用户编号 + * @param orderId 订单编号 + * @param cancelTypeEnum 取消类型 */ - void cancelPaidOrder(Long userId, Long orderId); + void cancelPaidOrder(Long userId, Long orderId, TradeOrderCancelTypeEnum cancelTypeEnum); } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java index c005781e3..36195c117 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java @@ -858,13 +858,13 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @Override @Transactional(rollbackFor = Exception.class) - public void cancelPaidOrder(Long userId, Long orderId) { - // TODO @puhui999:可能要加一个拼团取消;TradeOrderCancelTypeEnum.AFTER_SALE_CLOSE;然后参数传入下; + public void cancelPaidOrder(Long userId, Long orderId, TradeOrderCancelTypeEnum cancelTypeEnum) { // 1.1 检验订单存在 TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(orderId, userId); if (order == null) { throw exception(ORDER_NOT_FOUND); } + // 1.2 校验订单是否支付 if (!order.getPayStatus()) { throw exception(ORDER_CANCEL_PAID_FAIL, "已支付"); @@ -875,13 +875,13 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { } // 2.1 取消订单 - cancelOrder0(order, TradeOrderCancelTypeEnum.AFTER_SALE_CLOSE); + cancelOrder0(order, cancelTypeEnum); // 2.2 创建退款单 payRefundApi.createRefund(new PayRefundCreateReqDTO() .setAppKey(tradeOrderProperties.getPayAppKey()).setUserIp(getClientIP()) // 支付应用 .setMerchantOrderId(String.valueOf(order.getId())) // 支付单号 .setMerchantRefundId(String.valueOf(order.getId())) - .setReason("取消支付订单").setPrice(order.getPayPrice()));// 价格信息 + .setReason(cancelTypeEnum.getName()).setPrice(order.getPayPrice()));// 价格信息 } /** From 1c1abae5bbf8357fb32044e766cb92b06603dcfe Mon Sep 17 00:00:00 2001 From: scholar <1145227973@qq.com> Date: Wed, 28 Aug 2024 10:51:19 +0800 Subject: [PATCH 22/42] =?UTF-8?q?=E4=B8=83=E7=89=9B=E4=BA=91=E7=9F=AD?= =?UTF-8?q?=E4=BF=A1=E5=AE=9E=E7=8E=B0=EF=BC=8C=E8=AF=84=E5=AE=A1=E6=84=8F?= =?UTF-8?q?=E8=A7=81=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../framework/common/util/http/HttpUtils.java | 2 - .../sms/core/client/impl/QiniuSmsClient.java | 85 ++++++++----------- .../core/client/impl/QiniuSmsClientTest.java | 25 ++++-- .../sms/core/client/impl/SmsClientTests.java | 2 - 4 files changed, 51 insertions(+), 63 deletions(-) diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java index 1697d097f..456b4007e 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java @@ -135,7 +135,6 @@ public class HttpUtils { * @return 请求结果 */ public static String post(String url, Map headers, String requestBody) { - try (HttpResponse response = HttpRequest.post(url) .addHeaders(headers) .body(requestBody) @@ -154,7 +153,6 @@ public class HttpUtils { * @return 请求结果 */ public static String get(String url, Map headers) { - try (HttpResponse response = HttpRequest.get(url) .addHeaders(headers) .execute()) { diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClient.java index c0a2b60ac..4fbb8649d 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClient.java @@ -1,10 +1,13 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; +import cn.hutool.core.collection.CollStreamUtil; import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjectUtil; -import cn.hutool.crypto.digest.HMac; +import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.digest.HmacAlgorithm; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; @@ -18,11 +21,7 @@ import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProp import com.google.common.annotations.VisibleForTesting; import lombok.extern.slf4j.Slf4j; -import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; - import java.util.*; -import java.util.stream.Collectors; /** * 七牛云短信客户端的实现类 @@ -45,69 +44,60 @@ public class QiniuSmsClient extends AbstractSmsClient { Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); } - @Override protected void doInit() { } - @Override + public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, List> templateParams) throws Throwable { - // 1. 执行请求 // 参考链接 https://developer.qiniu.com/sms/5824/through-the-api-send-text-messages LinkedHashMap body = new LinkedHashMap<>(); - Map paramsMap = templateParams.stream() - .collect(Collectors.toMap(KeyValue::getKey, KeyValue::getValue)); - body.put("template_id", apiTemplateId); body.put("mobile", mobile); - body.put("parameters", paramsMap); + body.put("parameters", CollStreamUtil.toMap(templateParams, KeyValue::getKey, KeyValue::getValue)); body.put("seq", Long.toString(sendLogId)); - JSONObject response = request("POST", body, null); + JSONObject response = request("POST", body, PATH); // 2. 解析请求 + if (ObjectUtil.isNotEmpty(response.getStr("error"))){//短信请求失败 + return new SmsSendRespDTO().setSuccess(false) + .setApiCode(response.getStr("error")) + .setApiRequestId(response.getStr("request_id")) + .setApiMsg(response.getStr("message")); + } + return new SmsSendRespDTO().setSuccess(response.containsKey("message_id")) .setSerialNo(response.getStr("message_id")); } - /** * 请求七牛云短信 * * @see * @param httpMethod http请求方法 - * @param queryParams 请求参数 + * @param body http请求消息体 + * @param path URL path * @return 请求结果 */ - private JSONObject request(String httpMethod, LinkedHashMap body, Map queryParams) { - - String signature = ""; - String templateIdPath = ""; - - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); - dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - String signDate = dateFormat.format(new Date()); - + private JSONObject request(String httpMethod, LinkedHashMap body, String path) { + String signDate = DateUtil.date().setTimeZone(TimeZone.getTimeZone("UTC")).toString("yyyyMMdd'T'HHmmss'Z'"); //请求头 Map header = new HashMap<>(4); header.put("HOST", HOST); - header.put("Authorization", signature); + header.put("Authorization", getSignature(httpMethod, HOST, path, body != null ? JSONUtil.toJsonStr(body) : "", signDate)); header.put("Content-Type", "application/json"); header.put("X-Qiniu-Date", signDate); String responseBody =""; - if(Objects.equals(httpMethod, "POST")){ - header.put("Authorization", getSignature(httpMethod, HOST, PATH, JSONUtil.toJsonStr(body), signDate)); - responseBody = HttpUtils.post("https://" + HOST + PATH, header, JSONUtil.toJsonStr(body)); - }else { // GET - templateIdPath = TEMPLATE_PATH + "/" + queryParams.get("template_id"); - header.put("Authorization", getSignature(httpMethod, HOST, templateIdPath, null, signDate)); - responseBody = HttpUtils.get("https://" + HOST + templateIdPath, header); + if (Objects.equals(httpMethod, "POST")){// POST 发送短消息用POST请求 + responseBody = HttpUtils.post("https://" + HOST + path, header, JSONUtil.toJsonStr(body)); + }else { // GET 查询template状态用GET请求 + responseBody = HttpUtils.get("https://" + HOST + path, header); } return JSONUtil.parseObj(responseBody); } public String getSignature(String method, String host, String path, String body, String signDate) { - StringBuilder dataToSign = new StringBuilder(); dataToSign.append(method.toUpperCase()).append(" ").append(path); dataToSign.append("\nHost: ").append(host); @@ -117,18 +107,15 @@ public class QiniuSmsClient extends AbstractSmsClient { if (ObjectUtil.isNotEmpty(body)) { dataToSign.append(body); } - HMac hMac = new HMac(HmacAlgorithm.HmacSHA1, properties.getApiSecret().getBytes(StandardCharsets.UTF_8)); - byte[] signData = hMac.digest(dataToSign.toString().getBytes(StandardCharsets.UTF_8)); - String encodedSignature = Base64.getEncoder().encodeToString(signData); + String encodedSignature = SecureUtil.hmac(HmacAlgorithm.HmacSHA1, properties.getApiSecret()).digestBase64(dataToSign.toString(), true); return "Qiniu " + properties.getApiKey() + ":" + encodedSignature; } @Override public List parseSmsReceiveStatus(String text) { - JSONObject status = JSONUtil.parseObj(text); - //字段参考 https://developer.qiniu.com/sms/5910/message-push + // 字段参考 https://developer.qiniu.com/sms/5910/message-push return ListUtil.of(new SmsReceiveRespDTO() .setSuccess("DELIVRD".equals(status.getJSONArray("items").getJSONObject(0).getStr("status"))) // 是否接收成功 .setErrorMsg(status.getJSONArray("items").getJSONObject(0).getStr("status")) @@ -142,16 +129,13 @@ public class QiniuSmsClient extends AbstractSmsClient { public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable { // 1. 执行请求 // 参考链接 https://developer.qiniu.com/sms/5969/query-a-single-template - HashMap queryParam = new HashMap<>(); - queryParam.put("template_id", apiTemplateId); - JSONObject response = request("GET", null, queryParam); - + JSONObject response = request("GET", null, TEMPLATE_PATH + "/" + apiTemplateId); // 2.1 请求失败 - String status = response.getStr("audit_status"); - if (!Objects.equals(status, "passed")) { + if (ObjUtil.notEqual(response.getStr("audit_status"), "passed")) { log.error("[getSmsTemplate][模版编号({}) 响应不正确({})]", apiTemplateId, response); return null; } + // 2.2 请求成功 return new SmsTemplateRespDTO() .setId(response.getStr("id")) @@ -162,11 +146,12 @@ public class QiniuSmsClient extends AbstractSmsClient { @VisibleForTesting Integer convertSmsTemplateAuditStatus(String templateStatus) { - - if(Objects.equals(templateStatus, "passed")){ - return SmsTemplateAuditStatusEnum.SUCCESS.getStatus(); - }else { - throw new IllegalArgumentException(String.format("未知审核状态(%str)", templateStatus)); - } + return switch (templateStatus) { + case "passed" -> SmsTemplateAuditStatusEnum.SUCCESS.getStatus(); + case "reviewing" -> SmsTemplateAuditStatusEnum.CHECKING.getStatus(); + case "rejected" -> SmsTemplateAuditStatusEnum.FAIL.getStatus(); + case null, default -> + throw new IllegalArgumentException(String.format("未知审核状态(%str)", templateStatus)); + }; } } diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClientTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClientTest.java index c64c39470..c3e896695 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClientTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClientTest.java @@ -29,7 +29,6 @@ import static org.mockito.Mockito.mockStatic; * @author scholar */ public class QiniuSmsClientTest extends BaseMockitoUnitTest { - private final SmsChannelProperties properties = new SmsChannelProperties() .setApiKey(randomString())// 随机一个 apiKey,避免构建报错 .setApiSecret(randomString()) // 随机一个 apiSecret,避免构建报错 @@ -46,7 +45,6 @@ public class QiniuSmsClientTest extends BaseMockitoUnitTest { @Test public void testDoSendSms_success() throws Throwable { - try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { // 准备参数 Long sendLogId = randomLongId(); @@ -56,9 +54,7 @@ public class QiniuSmsClientTest extends BaseMockitoUnitTest { new KeyValue<>("1", 1234), new KeyValue<>("2", "login")); // mock 方法 httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) - .thenReturn( - "{\"message_id\":\"17245678901\"}" - ); + .thenReturn("{\"message_id\":\"17245678901\"}"); // 调用 SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams); @@ -77,17 +73,17 @@ public class QiniuSmsClientTest extends BaseMockitoUnitTest { String apiTemplateId = randomString() + " " + randomString(); List> templateParams = Lists.newArrayList( new KeyValue<>("1", 1234), new KeyValue<>("2", "login")); - // mock 方法 httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) - .thenReturn( - "{\"error\":\"BadToken\",\"message\":\"Your authorization token is invalid\",\"request_id\":\"etziWcJFo1C8Ne8X\"}" - ); + .thenReturn("{\"error\":\"BadToken\",\"message\":\"Your authorization token is invalid\",\"request_id\":\"etziWcJFo1C8Ne8X\"}"); // 调用 SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams); // 断言 assertFalse(result.getSuccess()); + assertEquals("BadToken", result.getApiCode()); + assertEquals("Your authorization token is invalid", result.getApiMsg()); + assertEquals("etziWcJFo1C8Ne8X", result.getApiRequestId()); } } @@ -125,4 +121,15 @@ public class QiniuSmsClientTest extends BaseMockitoUnitTest { assertEquals(123, statuses.getFirst().getLogId()); } + @Test + public void testConvertSmsTemplateAuditStatus() { + assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), + smsClient.convertSmsTemplateAuditStatus("passed")); + assertEquals(SmsTemplateAuditStatusEnum.CHECKING.getStatus(), + smsClient.convertSmsTemplateAuditStatus("reviewing")); + assertEquals(SmsTemplateAuditStatusEnum.FAIL.getStatus(), + smsClient.convertSmsTemplateAuditStatus("rejected")); + assertThrows(IllegalArgumentException.class, () -> smsClient.convertSmsTemplateAuditStatus("unknown"), + "未知审核状态(3)"); + } } \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java index 3752e5763..4f003ebaf 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java @@ -116,7 +116,6 @@ public class SmsClientTests { @Test @Disabled public void testQiniuSmsClient_sendSms() throws Throwable { - SmsChannelProperties properties = new SmsChannelProperties() .setApiKey("SMS_QINIU_ACCESS_KEY") .setApiSecret("SMS_QINIU_SECRET_KEY"); @@ -135,7 +134,6 @@ public class SmsClientTests { @Test @Disabled public void testQiniuSmsClient_getSmsTemplate() throws Throwable { - SmsChannelProperties properties = new SmsChannelProperties() .setApiKey("SMS_QINIU_ACCESS_KEY") .setApiSecret("SMS_QINIU_SECRET_KEY"); From cccad2c6c1974dc7c98fcfaa12c86321bab7bf6f Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 28 Aug 2024 13:19:36 +0800 Subject: [PATCH 23/42] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91=E5=95=86=E5=9F=8E=EF=BC=9A=E5=8F=96=E6=B6=88?= =?UTF-8?q?=E6=8B=BC=E5=9B=A2=E7=9A=84=E6=94=AF=E4=BB=98=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/PromotionProductScopeEnum.java | 8 ++--- .../app/activity/AppActivityController.java | 35 ++++++++++--------- .../service/reward/RewardActivityService.java | 2 +- .../reward/RewardActivityServiceImpl.java | 2 +- .../order/TradeOrderUpdateService.java | 7 ++-- .../order/TradeOrderUpdateServiceImpl.java | 7 ++-- .../TradeRewardActivityPriceCalculator.java | 7 ++-- 7 files changed, 35 insertions(+), 33 deletions(-) diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/common/PromotionProductScopeEnum.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/common/PromotionProductScopeEnum.java index c082e190f..4a95cb1fa 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/common/PromotionProductScopeEnum.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/common/PromotionProductScopeEnum.java @@ -1,11 +1,11 @@ package cn.iocoder.yudao.module.promotion.enums.common; -import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.common.core.IntArrayValuable; import lombok.AllArgsConstructor; import lombok.Getter; import java.util.Arrays; +import java.util.Objects; /** * 营销的商品范围枚举 @@ -37,15 +37,15 @@ public enum PromotionProductScopeEnum implements IntArrayValuable { } public static boolean isAll(Integer scope) { - return ObjUtil.equal(scope, ALL.scope); + return Objects.equals(scope, ALL.scope); } public static boolean isSpu(Integer scope) { - return ObjUtil.equal(scope, SPU.scope); + return Objects.equals(scope, SPU.scope); } public static boolean isCategory(Integer scope) { - return ObjUtil.equal(scope, CATEGORY.scope); + return Objects.equals(scope, CATEGORY.scope); } } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/AppActivityController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/AppActivityController.java index fe15c0f71..fae7fa54d 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/AppActivityController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/AppActivityController.java @@ -53,6 +53,7 @@ public class AppActivityController { private DiscountActivityService discountActivityService; @Resource private RewardActivityService rewardActivityService; + @Resource private ProductSpuApi productSpuApi; @@ -91,7 +92,7 @@ public class AppActivityController { // 4. 限时折扣活动 getDiscountActivities(spuIds, now, activityList); // 5. 满减送活动 - getRewardActivities(spuIds, now, activityList); + getRewardActivityList(spuIds, now, activityList); return activityList; } @@ -148,23 +149,9 @@ public class AppActivityController { item.getName(), productMap.get(item.getId()), item.getStartTime(), item.getEndTime()))); } - private static void buildAppActivityRespVO(RewardActivityDO rewardActivity, Collection spuIds, - List activityList) { - for (Long spuId : spuIds) { - // 校验商品是否已经加入过活动 - if (anyMatch(activityList, appActivity -> ObjUtil.equal(appActivity.getId(), rewardActivity.getId()) && - ObjUtil.equal(appActivity.getSpuId(), spuId))) { - continue; - } - activityList.add(new AppActivityRespVO(rewardActivity.getId(), - PromotionTypeEnum.REWARD_ACTIVITY.getType(), rewardActivity.getName(), spuId, - rewardActivity.getStartTime(), rewardActivity.getEndTime())); - } - } - - private void getRewardActivities(Collection spuIds, LocalDateTime now, List activityList) { + private void getRewardActivityList(Collection spuIds, LocalDateTime now, List activityList) { // 1.1 获得所有的活动 - List rewardActivityList = rewardActivityService.getRewardActivityByStatusAndDateTimeLt( + List rewardActivityList = rewardActivityService.getRewardActivityListByStatusAndDateTimeLt( CommonStatusEnum.ENABLE.getStatus(), now); if (CollUtil.isEmpty(rewardActivityList)) { return; @@ -196,4 +183,18 @@ public class AppActivityController { } } + private static void buildAppActivityRespVO(RewardActivityDO rewardActivity, Collection spuIds, + List activityList) { + for (Long spuId : spuIds) { + // 校验商品是否已经加入过活动 + if (anyMatch(activityList, appActivity -> ObjUtil.equal(appActivity.getId(), rewardActivity.getId()) && + ObjUtil.equal(appActivity.getSpuId(), spuId))) { + continue; + } + activityList.add(new AppActivityRespVO(rewardActivity.getId(), + PromotionTypeEnum.REWARD_ACTIVITY.getType(), rewardActivity.getName(), spuId, + rewardActivity.getStartTime(), rewardActivity.getEndTime())); + } + } + } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityService.java index 1d4b978e9..27cc86c33 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityService.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityService.java @@ -79,6 +79,6 @@ public interface RewardActivityService { * @param dateTime 当前日期时间 * @return 满减送活动列表 */ - List getRewardActivityByStatusAndDateTimeLt(Integer status, LocalDateTime dateTime); + List getRewardActivityListByStatusAndDateTimeLt(Integer status, LocalDateTime dateTime); } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java index a6a865eab..1ad0ae48f 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java @@ -165,7 +165,7 @@ public class RewardActivityServiceImpl implements RewardActivityService { } @Override - public List getRewardActivityByStatusAndDateTimeLt(Integer status, LocalDateTime dateTime) { + public List getRewardActivityListByStatusAndDateTimeLt(Integer status, LocalDateTime dateTime) { return rewardActivityMapper.selectListByStatusAndDateTimeLt(status, dateTime); } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java index b38decc17..d03826924 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java @@ -186,15 +186,14 @@ public interface TradeOrderUpdateService { */ void updateOrderCombinationInfo(Long orderId, Long activityId, Long combinationRecordId, Long headId); - // TODO 芋艿:拼团取消,不调这个接口哈; - + // TODO @puhui999:不传递枚举哈。因为 rpc 不好支持。 /** * 取消支付订单 * * @param userId 用户编号 * @param orderId 订单编号 - * @param cancelTypeEnum 取消类型 + * @param cancelType 取消类型 */ - void cancelPaidOrder(Long userId, Long orderId, TradeOrderCancelTypeEnum cancelTypeEnum); + void cancelPaidOrder(Long userId, Long orderId, TradeOrderCancelTypeEnum cancelType); } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java index 36195c117..3eda99411 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java @@ -858,7 +858,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @Override @Transactional(rollbackFor = Exception.class) - public void cancelPaidOrder(Long userId, Long orderId, TradeOrderCancelTypeEnum cancelTypeEnum) { + public void cancelPaidOrder(Long userId, Long orderId, TradeOrderCancelTypeEnum cancelType) { + // TODO @puhui999:这里校验下 cancelType 只允许拼团关闭; // 1.1 检验订单存在 TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(orderId, userId); if (order == null) { @@ -875,13 +876,13 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { } // 2.1 取消订单 - cancelOrder0(order, cancelTypeEnum); + cancelOrder0(order, cancelType); // 2.2 创建退款单 payRefundApi.createRefund(new PayRefundCreateReqDTO() .setAppKey(tradeOrderProperties.getPayAppKey()).setUserIp(getClientIP()) // 支付应用 .setMerchantOrderId(String.valueOf(order.getId())) // 支付单号 .setMerchantRefundId(String.valueOf(order.getId())) - .setReason(cancelTypeEnum.getName()).setPrice(order.getPayPrice()));// 价格信息 + .setReason(cancelType.getName()).setPrice(order.getPayPrice()));// 价格信息 } /** diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java index 0c25dcb30..490c2aea7 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java @@ -52,7 +52,7 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator private void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result, RewardActivityMatchRespDTO rewardActivity) { // 1.1 获得满减送的订单项(商品)列表 - List orderItems = filterMatchCouponOrderItems(result, rewardActivity); + List orderItems = filterMatchActivityOrderItems(result, rewardActivity); if (CollUtil.isEmpty(orderItems)) { return; } @@ -93,8 +93,9 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator * @param rewardActivity 满减送活动 * @return 订单项(商品)列表 */ - private List filterMatchCouponOrderItems(TradePriceCalculateRespBO result, - RewardActivityMatchRespDTO rewardActivity) { + private List filterMatchActivityOrderItems(TradePriceCalculateRespBO result, + RewardActivityMatchRespDTO rewardActivity) { + // TODO @puhui999:是不是得根据类型过滤哈 return filterList(result.getItems(), orderItem -> CollUtil.contains(rewardActivity.getProductScopeValues(), orderItem.getSpuId())); } From e0d9f7cfbaccf578b9b0351f8eec077404183c7e Mon Sep 17 00:00:00 2001 From: puhui999 Date: Thu, 29 Aug 2024 18:19:54 +0800 Subject: [PATCH 24/42] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91=E5=95=86=E5=9F=8E:=20=E6=BB=A1=E5=87=8F?= =?UTF-8?q?=E9=80=81=E6=B4=BB=E5=8A=A8=E4=B8=8B=E5=8D=95=E5=90=8E=EF=BC=8C?= =?UTF-8?q?=E8=B5=A0=E9=80=81=E7=A7=AF=E5=88=86=E3=80=81=E4=BC=98=E6=83=A0?= =?UTF-8?q?=E5=8A=B5=E3=80=81=E5=8C=85=E9=82=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/reward/vo/RewardActivityBaseVO.java | 2 +- .../CombinationRecordServiceImpl.java | 2 +- .../module/trade/api/order/TradeOrderApi.java | 5 +- .../trade/enums/ErrorCodeConstants.java | 1 + .../trade/api/order/TradeOrderApiImpl.java | 5 +- .../dataobject/order/TradeOrderItemDO.java | 13 ++++ .../order/TradeOrderUpdateService.java | 6 +- .../order/TradeOrderUpdateServiceImpl.java | 11 +-- .../handler/TradeCouponOrderHandler.java | 7 +- .../price/bo/TradePriceCalculateRespBO.java | 30 +++++++- .../TradeDeliveryPriceCalculator.java | 20 +++--- .../TradePriceCalculatorHelper.java | 6 +- .../TradeRewardActivityPriceCalculator.java | 68 ++++++++++++++++--- 13 files changed, 135 insertions(+), 41 deletions(-) diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java index d498b5e9f..f932a58d6 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java @@ -96,7 +96,7 @@ public class RewardActivityBaseVO { @AssertTrue(message = "优惠劵和数量必须一一对应") @JsonIgnore public boolean isCouponCountsValid() { - return BooleanUtil.isFalse(givePoint) || CollUtil.size(couponIds) == CollUtil.size(couponCounts); + return BooleanUtil.isFalse(giveCoupon) || CollUtil.size(couponIds) == CollUtil.size(couponCounts); } @AssertTrue(message = "赠送的积分不能小于 1") diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java index c1449d60d..6f5ac3f62 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java @@ -340,7 +340,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { CombinationRecordStatusEnum.FAILED); // 2. 订单取消 headAndRecords.forEach(item -> tradeOrderApi.cancelPaidOrder(item.getUserId(), item.getOrderId(), - TradeOrderCancelTypeEnum.COMBINATION_CLOSE)); + TradeOrderCancelTypeEnum.COMBINATION_CLOSE.getType())); } /** diff --git a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java index d21e88a44..64a269482 100644 --- a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java +++ b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java @@ -1,7 +1,6 @@ package cn.iocoder.yudao.module.trade.api.order; import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO; -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderCancelTypeEnum; import java.util.Collection; import java.util.List; @@ -34,8 +33,8 @@ public interface TradeOrderApi { * * @param userId 用户编号 * @param orderId 订单编号 - * @param cancelTypeEnum 取消类型 + * @param cancelType 取消类型 */ - void cancelPaidOrder(Long userId, Long orderId, TradeOrderCancelTypeEnum cancelTypeEnum); + void cancelPaidOrder(Long userId, Long orderId, Integer cancelType); } diff --git a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java index 696eeba1b..a797fa5bd 100644 --- a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java +++ b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java @@ -60,6 +60,7 @@ public interface ErrorCodeConstants { ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND = new ErrorCode(1_011_003_002, "计算快递运费异常,找不到对应的运费模板"); ErrorCode PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER = new ErrorCode(1_011_003_004, "参与秒杀、拼团、砍价的营销商品,无法使用优惠劵"); ErrorCode PRICE_CALCULATE_SECKILL_TOTAL_LIMIT_COUNT = new ErrorCode(1_011_003_005, "参与秒杀的商品,超过了秒杀总限购数量"); + ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TYPE_ILLEGAL = new ErrorCode(1_011_003_006, "计算快递运费异常,配送方式不匹配"); // ========== 物流 Express 模块 1-011-004-000 ========== ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1_011_004_000, "快递公司不存在"); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApiImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApiImpl.java index edb675f29..5e50f43ab 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApiImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApiImpl.java @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.trade.api.order; import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO; import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert; -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderCancelTypeEnum; import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService; import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService; import jakarta.annotation.Resource; @@ -37,8 +36,8 @@ public class TradeOrderApiImpl implements TradeOrderApi { } @Override - public void cancelPaidOrder(Long userId, Long orderId, TradeOrderCancelTypeEnum cancelTypeEnum) { - tradeOrderUpdateService.cancelPaidOrder(userId, orderId, cancelTypeEnum); + public void cancelPaidOrder(Long userId, Long orderId, Integer cancelType) { + tradeOrderUpdateService.cancelPaidOrder(userId, orderId, cancelType); } } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderItemDO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderItemDO.java index 450bc764f..b69997605 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderItemDO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderItemDO.java @@ -160,6 +160,19 @@ public class TradeOrderItemDO extends BaseDO { */ private Integer vipPrice; + /** + * 赠送的优惠劵编号的数组 + * + * 目的:用于后续取消或者售后订单时,需要扣减赠送 + */ + private List couponIds; + /** + * 赠送的优惠券数量的数组 + * + * 目的:用于后续取消或者售后订单时,需要扣减赠送 + */ + private List couponCounts; + // ========== 售后基本信息 ========== /** diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java index d03826924..4508138ff 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java @@ -9,7 +9,6 @@ import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettle import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO; import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderCancelTypeEnum; import jakarta.validation.constraints.NotNull; /** @@ -186,14 +185,13 @@ public interface TradeOrderUpdateService { */ void updateOrderCombinationInfo(Long orderId, Long activityId, Long combinationRecordId, Long headId); - // TODO @puhui999:不传递枚举哈。因为 rpc 不好支持。 /** * 取消支付订单 * * @param userId 用户编号 * @param orderId 订单编号 - * @param cancelType 取消类型 + * @param cancelType 取消类型 */ - void cancelPaidOrder(Long userId, Long orderId, TradeOrderCancelTypeEnum cancelType); + void cancelPaidOrder(Long userId, Long orderId, Integer cancelType); } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java index 3eda99411..a4f29a5b8 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java @@ -858,8 +858,11 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @Override @Transactional(rollbackFor = Exception.class) - public void cancelPaidOrder(Long userId, Long orderId, TradeOrderCancelTypeEnum cancelType) { - // TODO @puhui999:这里校验下 cancelType 只允许拼团关闭; + public void cancelPaidOrder(Long userId, Long orderId, Integer cancelType) { + // 1. 这里校验下 cancelType 只允许拼团关闭; + if (!TradeOrderCancelTypeEnum.COMBINATION_CLOSE.getType().equals(cancelType)) { + return; + } // 1.1 检验订单存在 TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(orderId, userId); if (order == null) { @@ -876,13 +879,13 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { } // 2.1 取消订单 - cancelOrder0(order, cancelType); + cancelOrder0(order, TradeOrderCancelTypeEnum.COMBINATION_CLOSE); // 2.2 创建退款单 payRefundApi.createRefund(new PayRefundCreateReqDTO() .setAppKey(tradeOrderProperties.getPayAppKey()).setUserIp(getClientIP()) // 支付应用 .setMerchantOrderId(String.valueOf(order.getId())) // 支付单号 .setMerchantRefundId(String.valueOf(order.getId())) - .setReason(cancelType.getName()).setPrice(order.getPayPrice()));// 价格信息 + .setReason(TradeOrderCancelTypeEnum.COMBINATION_CLOSE.getName()).setPrice(order.getPayPrice()));// 价格信息 } /** diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java index 478de450f..eb4816719 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java @@ -4,9 +4,9 @@ import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi; import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; +import jakarta.annotation.Resource; import org.springframework.stereotype.Component; -import jakarta.annotation.Resource; import java.util.List; /** @@ -30,6 +30,11 @@ public class TradeCouponOrderHandler implements TradeOrderHandler { .setOrderId(order.getId())); } + @Override + public void afterPayOrder(TradeOrderDO order, List orderItems) { + + } + @Override public void afterCancelOrder(TradeOrderDO order, List orderItems) { if (order.getCouponId() == null || order.getCouponId() <= 0) { diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java index b7482407c..32043df0e 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java @@ -67,6 +67,21 @@ public class TradePriceCalculateRespBO { */ private Long bargainActivityId; + /** + * 是否包邮 + */ + private Boolean freeDelivery; + + // TODO @puhui999: 订单保存时需要保存 + /** + * 赠送的优惠劵编号的数组 + */ + private List couponIds; + /** + * 赠送的优惠券数量的数组 + */ + private List couponCounts; + /** * 订单价格 */ @@ -213,8 +228,19 @@ public class TradePriceCalculateRespBO { */ private Long categoryId; + // ========== 物流相关字段 ========= + /** - * 运费模板 Id + * 配送方式数组 + * + * 对应 DeliveryTypeEnum 枚举 + */ + private List deliveryTypes; + + /** + * 物流配置模板编号 + * + * 对应 TradeDeliveryExpressTemplateDO 的 id 编号 */ private Long deliveryTemplateId; @@ -234,7 +260,7 @@ public class TradePriceCalculateRespBO { private List properties; /** - * 使用的积分 + * 赠送的积分 */ private Integer givePoint; diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java index 2fa0d44af..d0dcf9cfd 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java @@ -6,8 +6,6 @@ import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.module.member.api.address.MemberAddressApi; import cn.iocoder.yudao.module.member.api.address.dto.MemberAddressRespDTO; -import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; -import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO; import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryPickUpStoreDO; import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryExpressChargeModeEnum; @@ -30,8 +28,7 @@ import java.util.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; -import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PICK_UP_STORE_NOT_EXISTS; -import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND; +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*; /** * 运费的 {@link TradePriceCalculator} 实现类 @@ -52,19 +49,15 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { private DeliveryExpressTemplateService deliveryExpressTemplateService; @Resource private TradeConfigService tradeConfigService; - @Resource - private ProductSpuApi productSpuApi; @Override public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { if (param.getDeliveryType() == null) { return; } - // TODO @puhui999:1)TradePriceCalculateRespBO 传递进来 delveryType 配送方式,减少读取;2)如果不匹配,抛出业务异常; = = 不然就不扣钱啦。 // 校验是不是存在商品不能门店自提,或者不能快递发货的情况。就是说,配送方式不匹配哈 - List spuList = productSpuApi.getSpuList(convertSet(result.getItems(), OrderItem::getSpuId)); - if (anyMatch(spuList, item -> !item.getDeliveryTypes().contains(param.getDeliveryType()))) { - return; + if (anyMatch(result.getItems(), item -> !item.getDeliveryTypes().contains(param.getDeliveryType()))) { + throw exception(PRICE_CALCULATE_DELIVERY_PRICE_TYPE_ILLEGAL); } if (DeliveryTypeEnum.PICK_UP.getType().equals(param.getDeliveryType())) { @@ -101,7 +94,12 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { return; } - // 情况二:快递模版 + // 情况二:活动包邮 + if (Boolean.TRUE.equals(result.getFreeDelivery())) { + return; + } + + // 情况三:快递模版 // 2.1 过滤出已选中的商品 SKU List selectedItem = filterList(result.getItems(), OrderItem::getSelected); Set deliveryTemplateIds = convertSet(selectedItem, OrderItem::getDeliveryTemplateId); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java index 891f1e0dc..cb8f97c11 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java @@ -31,8 +31,8 @@ public class TradePriceCalculatorHelper { List spuList, List skuList) { // 创建 PriceCalculateRespDTO 对象 TradePriceCalculateRespBO result = new TradePriceCalculateRespBO(); - result.setType(getOrderType(param)); - result.setPromotions(new ArrayList<>()); + result.setType(getOrderType(param)).setPromotions(new ArrayList<>()) + .setCouponIds(new ArrayList<>()).setCouponCounts(new ArrayList<>()); // 创建它的 OrderItem 属性 result.setItems(new ArrayList<>(param.getItems().size())); @@ -60,7 +60,7 @@ public class TradePriceCalculatorHelper { .setWeight(sku.getWeight()).setVolume(sku.getVolume()); // spu 信息 orderItem.setSpuName(spu.getName()).setCategoryId(spu.getCategoryId()) - .setDeliveryTemplateId(spu.getDeliveryTemplateId()) + .setDeliveryTypes(spu.getDeliveryTypes()).setDeliveryTemplateId(spu.getDeliveryTemplateId()) .setGivePoint(spu.getGiveIntegral()).setUsePoint(0); if (StrUtil.isBlank(orderItem.getPicUrl())) { orderItem.setPicUrl(spu.getPicUrl()); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java index 490c2aea7..fa5a61f3a 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java @@ -3,9 +3,11 @@ package cn.iocoder.yudao.module.trade.service.price.calculator; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; import cn.iocoder.yudao.module.promotion.api.reward.RewardActivityApi; import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO; import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum; +import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum; import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; @@ -14,6 +16,8 @@ import jakarta.annotation.Resource; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; +import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; @@ -61,7 +65,7 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator if (rule == null) { TradePriceCalculatorHelper.addNotMatchPromotion(result, orderItems, rewardActivity.getId(), rewardActivity.getName(), PromotionTypeEnum.REWARD_ACTIVITY.getType(), - getRewardActivityNotMeetTip(rewardActivity)); + getRewardActivityNotMeetTip(rewardActivity, orderItems)); return; } @@ -84,6 +88,26 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator TradePriceCalculatorHelper.recountPayPrice(orderItem); } TradePriceCalculatorHelper.recountAllPrice(result); + + // 4.1 记录赠送的积分 + if (rule.getGivePoint()) { + List dividePoints = TradePriceCalculatorHelper.dividePrice(orderItems, rule.getPoint()); + for (int i = 0; i < orderItems.size(); i++) { + TradePriceCalculateRespBO.OrderItem orderItem = orderItems.get(i); + // 商品可能赠送了积分,所以这里要加上 + orderItem.setGivePoint(orderItem.getGivePoint() + dividePoints.get(i)); + } + } + // 4.2 记录订单是否包邮 + if (rule.getFreeDelivery()) { + // 只要满足一个活动包邮那么这单就包邮 + result.setFreeDelivery(true); + } + // 4.3 记录赠送的优惠券 + if (rule.getGiveCoupon()) { + // TODO @puhui999: 需要考虑赠送的优惠券是否重叠,重叠则对数量进行累加 + result.setCouponIds(rule.getCouponIds()).setCouponCounts(rule.getCouponCounts()); + } } /** @@ -95,9 +119,21 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator */ private List filterMatchActivityOrderItems(TradePriceCalculateRespBO result, RewardActivityMatchRespDTO rewardActivity) { - // TODO @puhui999:是不是得根据类型过滤哈 - return filterList(result.getItems(), - orderItem -> CollUtil.contains(rewardActivity.getProductScopeValues(), orderItem.getSpuId())); + // 情况一:全部商品都可以参与 + if (PromotionProductScopeEnum.isAll(rewardActivity.getProductScope())) { + return result.getItems(); + } + // 情况二:指定商品参与 + if (PromotionProductScopeEnum.isSpu(rewardActivity.getProductScope())) { + return filterList(result.getItems(), + orderItem -> CollUtil.contains(rewardActivity.getProductScopeValues(), orderItem.getSpuId())); + } + // 情况三:指定商品类型参与 + if (PromotionProductScopeEnum.isCategory(rewardActivity.getProductScope())) { + return filterList(result.getItems(), + orderItem -> CollUtil.contains(rewardActivity.getProductScopeValues(), orderItem.getCategoryId())); + } + return List.of(); } /** @@ -130,14 +166,30 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator } /** - * 获得满减送活动部匹配时的提示 + * 获得满减送活动不匹配时的提示 * * @param rewardActivity 满减送活动 * @return 提示 */ - private String getRewardActivityNotMeetTip(RewardActivityMatchRespDTO rewardActivity) { - // TODO 芋艿:后面再想想;应该找第一个规则,算下还差多少即可。 - return "TODO"; + private String getRewardActivityNotMeetTip(RewardActivityMatchRespDTO rewardActivity, + List orderItems) { + // 1. 计算数量和价格 + Integer count = TradePriceCalculatorHelper.calculateTotalCount(orderItems); + Integer price = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems); + assert count != null && price != null; + + // 2. 构建不满足时的提示信息-按最低档规则算 + String meetTip = "满减送:购满 {} {},可以减 {} 元"; + List rules = new ArrayList<>(rewardActivity.getRules()); + rules.sort(Comparator.comparing(RewardActivityMatchRespDTO.Rule::getLimit)); // 按优惠门槛降序 + RewardActivityMatchRespDTO.Rule rule = rules.get(0); + if (PromotionConditionTypeEnum.PRICE.getType().equals(rewardActivity.getConditionType())) { + return StrUtil.format(meetTip, rule.getLimit(), "元", MoneyUtils.fenToYuanStr(rule.getDiscountPrice())); + } + if (PromotionConditionTypeEnum.COUNT.getType().equals(rewardActivity.getConditionType())) { + return StrUtil.format(meetTip, rule.getLimit(), "件", MoneyUtils.fenToYuanStr(rule.getDiscountPrice())); + } + return StrUtil.EMPTY; } } From 3e66a922bf37a0446aaa244620ef5e0b99644c77 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Thu, 29 Aug 2024 21:45:44 +0800 Subject: [PATCH 25/42] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91=E5=95=86=E5=9F=8E:=20=E6=BB=A1=E5=87=8F?= =?UTF-8?q?=E9=80=81=E6=B4=BB=E5=8A=A8=E4=B8=8B=E5=8D=95=E5=90=8E=EF=BC=8C?= =?UTF-8?q?=E8=B5=A0=E9=80=81=E4=BC=98=E6=83=A0=E5=8A=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../promotion/api/coupon/CouponApi.java | 12 +++++++- .../promotion/api/coupon/CouponApiImpl.java | 8 ++++- .../service/coupon/CouponService.java | 9 ++++++ .../service/coupon/CouponServiceImpl.java | 30 +++++++++++++++++-- .../dal/dataobject/order/TradeOrderDO.java | 13 ++++++++ .../dataobject/order/TradeOrderItemDO.java | 13 -------- .../order/TradeOrderUpdateServiceImpl.java | 2 ++ .../handler/TradeCouponOrderHandler.java | 18 ++++++++--- .../price/bo/TradePriceCalculateRespBO.java | 1 - .../TradeRewardActivityPriceCalculator.java | 15 ++++++++-- 10 files changed, 96 insertions(+), 25 deletions(-) diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java index 27e5b6fb8..09fa7f9a8 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java @@ -3,9 +3,10 @@ package cn.iocoder.yudao.module.promotion.api.coupon; import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO; import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO; import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO; - import jakarta.validation.Valid; +import java.util.List; + /** * 优惠劵 API 接口 * @@ -35,4 +36,13 @@ public interface CouponApi { */ CouponRespDTO validateCoupon(@Valid CouponValidReqDTO validReqDTO); + /** + * 【管理员】给指定用户批量发送优惠券 + * + * @param templateIds 优惠劵编号的数组 + * @param counts 优惠券数量的数组 + * @param userId 用户编号 + */ + void takeCouponsByAdmin(List templateIds, List counts, Long userId); + } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java index b7f904583..23d088a74 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java @@ -7,10 +7,11 @@ import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO; import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert; import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO; import cn.iocoder.yudao.module.promotion.service.coupon.CouponService; +import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; -import jakarta.annotation.Resource; +import java.util.List; /** * 优惠劵 API 实现类 @@ -41,4 +42,9 @@ public class CouponApiImpl implements CouponApi { return CouponConvert.INSTANCE.convert(coupon); } + @Override + public void takeCouponsByAdmin(List templateIds, List counts, Long userId) { + couponService.takeCouponsByAdmin(templateIds, counts, userId); + } + } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java index edd654275..5220a6da7 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java @@ -105,6 +105,15 @@ public interface CouponService { takeCoupon(templateId, userIds, CouponTakeTypeEnum.ADMIN); } + /** + * 【管理员】给指定用户批量发送优惠券 + * + * @param templateIds 优惠劵编号的数组 + * @param counts 优惠券数量的数组 + * @param userId 用户编号 + */ + void takeCouponsByAdmin(List templateIds, List counts, Long userId); + /** * 【会员】领取优惠券 * diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java index abf933d83..dcca8344f 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java @@ -19,19 +19,19 @@ import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponMapper; import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum; import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum; import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityTypeEnum; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; -import jakarta.annotation.Resource; - import java.time.LocalDateTime; import java.util.*; import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*; import static java.util.Arrays.asList; @@ -175,10 +175,34 @@ public class CouponServiceImpl implements CouponService { // 3. 批量保存优惠劵 couponMapper.insertBatch(convertList(userIds, userId -> CouponConvert.INSTANCE.convert(template, userId))); - // 3. 增加优惠劵模板的领取数量 + // 4. 增加优惠劵模板的领取数量 couponTemplateService.updateCouponTemplateTakeCount(templateId, userIds.size()); } + @Override + public void takeCouponsByAdmin(List templateIds, List counts, Long userId) { + // 1. 获得优惠券模版 + List templateList = couponTemplateService.getCouponTemplateList(templateIds); + if (CollUtil.isEmpty(templateList)) { + return; + } + + Map templateMap = convertMap(templateList, CouponTemplateDO::getId); + // 2.1 批量构建优惠券 + List couponList = new ArrayList<>(); + for (int i = 0; i < templateIds.size(); i++) { + int finalI = i; + findAndThen(templateMap, templateIds.get(i), template -> { + for (int j = 0; j < counts.get(finalI); j++) { + couponList.add(CouponConvert.INSTANCE.convert(template, userId) + .setTakeType(CouponTakeTypeEnum.ADMIN.getValue())); + } + }); + } + // 2.2 批量保存优惠券 + couponMapper.insertBatch(couponList); + } + @Override @Transactional(rollbackFor = Exception.class) public void takeCouponByRegister(Long userId) { diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java index b127004aa..495287edf 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java @@ -16,6 +16,7 @@ import com.baomidou.mybatisplus.annotation.TableName; import lombok.*; import java.time.LocalDateTime; +import java.util.List; /** * 交易订单 DO @@ -290,6 +291,18 @@ public class TradeOrderDO extends BaseDO { * VIP 减免金额,单位:分 */ private Integer vipPrice; + /** + * 赠送的优惠劵编号的数组 + * + * 目的:用于后续取消或者售后订单时,需要扣减赠送 + */ + private List couponIds; + /** + * 赠送的优惠券数量的数组 + * + * 目的:用于后续取消或者售后订单时,需要扣减赠送 + */ + private List couponCounts; /** * 秒杀活动编号 diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderItemDO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderItemDO.java index b69997605..450bc764f 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderItemDO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderItemDO.java @@ -160,19 +160,6 @@ public class TradeOrderItemDO extends BaseDO { */ private Integer vipPrice; - /** - * 赠送的优惠劵编号的数组 - * - * 目的:用于后续取消或者售后订单时,需要扣减赠送 - */ - private List couponIds; - /** - * 赠送的优惠券数量的数组 - * - * 目的:用于后续取消或者售后订单时,需要扣减赠送 - */ - private List couponCounts; - // ========== 售后基本信息 ========== /** diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java index a4f29a5b8..ee6cb90b0 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java @@ -201,6 +201,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()); order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum)); order.setUserIp(getClientIP()).setTerminal(getTerminal()); + // 优惠券 + order.setCouponIds(calculateRespBO.getCouponIds()).setCouponCounts(calculateRespBO.getCouponCounts()); // 支付 + 退款信息 order.setAdjustPrice(0).setPayStatus(false); order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()).setRefundPrice(0); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java index eb4816719..6da931b6e 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.trade.service.order.handler; +import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi; import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; @@ -32,16 +33,25 @@ public class TradeCouponOrderHandler implements TradeOrderHandler { @Override public void afterPayOrder(TradeOrderDO order, List orderItems) { - + if (CollUtil.isEmpty(order.getCouponIds())) { + return; + } + // 赠送优惠券 + couponApi.takeCouponsByAdmin(order.getCouponIds(), order.getCouponCounts(), order.getUserId()); } @Override public void afterCancelOrder(TradeOrderDO order, List orderItems) { - if (order.getCouponId() == null || order.getCouponId() <= 0) { + // 情况一:退还订单使用的优惠券 + if (order.getCouponId() != null && order.getCouponId() > 0) { + // 退回优惠劵 + couponApi.returnUsedCoupon(order.getCouponId()); + } + // 情况二:收回赠送的优惠券 + if (CollUtil.isEmpty(order.getCouponIds())) { return; } - // 退回优惠劵 - couponApi.returnUsedCoupon(order.getCouponId()); + // TODO @puhui999: 收回优惠券再考虑一下,是直接删除券还是改个状态 } } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java index 32043df0e..119b68ec8 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java @@ -72,7 +72,6 @@ public class TradePriceCalculateRespBO { */ private Boolean freeDelivery; - // TODO @puhui999: 订单保存时需要保存 /** * 赠送的优惠劵编号的数组 */ diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java index fa5a61f3a..47420e24e 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java @@ -19,6 +19,7 @@ import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.Objects; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; @@ -105,8 +106,18 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator } // 4.3 记录赠送的优惠券 if (rule.getGiveCoupon()) { - // TODO @puhui999: 需要考虑赠送的优惠券是否重叠,重叠则对数量进行累加 - result.setCouponIds(rule.getCouponIds()).setCouponCounts(rule.getCouponCounts()); + for (int i = 0; i < rule.getCouponIds().size(); i++) { + Long couponId = result.getCouponIds().get(i); + Integer couponCount = result.getCouponCounts().get(i); + int index = CollUtil.indexOf(result.getCouponIds(), id -> Objects.equals(couponId, id)); + if (index != -1) { // 情况一:别的满减活动送过同类优惠券,则直接增加数量 + List couponCounts = result.getCouponCounts(); + couponCounts.set(index, couponCounts.get(index) + couponCount); + result.setCouponCounts(couponCounts); + } else { // 情况二:还没有赠送的优惠券 + result.setCouponIds(rule.getCouponIds()).setCouponCounts(rule.getCouponCounts()); + } + } } } From d60374d646e8087b1e3be0d405d2c4f09ddecc0c Mon Sep 17 00:00:00 2001 From: YunaiV Date: Thu, 29 Aug 2024 23:30:59 +0800 Subject: [PATCH 26/42] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91=E5=95=86=E5=9F=8E=EF=BC=9A=E6=BB=A1=E5=87=8F?= =?UTF-8?q?=E9=80=81=E8=AE=A2=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/module/promotion/api/coupon/CouponApi.java | 1 + .../promotion/service/coupon/CouponServiceImpl.java | 1 + .../yudao/module/trade/api/order/TradeOrderApi.java | 6 +++--- .../trade/dal/dataobject/order/TradeOrderDO.java | 2 ++ .../service/order/TradeOrderUpdateServiceImpl.java | 10 +++++----- .../service/order/handler/TradeCouponOrderHandler.java | 2 +- .../service/price/bo/TradePriceCalculateRespBO.java | 1 + .../price/calculator/TradeDeliveryPriceCalculator.java | 3 ++- .../calculator/TradeRewardActivityPriceCalculator.java | 5 +++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java index 09fa7f9a8..f7b741ddb 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java @@ -36,6 +36,7 @@ public interface CouponApi { */ CouponRespDTO validateCoupon(@Valid CouponValidReqDTO validReqDTO); + // TODO @puhui999:Map 优惠劵 会不会好点。 /** * 【管理员】给指定用户批量发送优惠券 * diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java index dcca8344f..222843c31 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java @@ -181,6 +181,7 @@ public class CouponServiceImpl implements CouponService { @Override public void takeCouponsByAdmin(List templateIds, List counts, Long userId) { + // TODO @puhui999:要不要循环调用上面的 takeCoupon 方法?按道理说,赠送也不会很多张。如果某次发卷失败,可以打个 error log; // 1. 获得优惠券模版 List templateList = couponTemplateService.getCouponTemplateList(templateIds); if (CollUtil.isEmpty(templateList)) { diff --git a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java index 64a269482..4bf1f5bf9 100644 --- a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java +++ b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java @@ -31,9 +31,9 @@ public interface TradeOrderApi { /** * 取消支付订单 * - * @param userId 用户编号 - * @param orderId 订单编号 - * @param cancelType 取消类型 + * @param userId 用户编号 + * @param orderId 订单编号 + * @param cancelType 取消类型 */ void cancelPaidOrder(Long userId, Long orderId, Integer cancelType); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java index 495287edf..4ce025408 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java @@ -291,6 +291,8 @@ public class TradeOrderDO extends BaseDO { * VIP 减免金额,单位:分 */ private Integer vipPrice; + + // TODO @puhui999::1)建议命名要 giveXXX;不然不好理解哈;2)是不是搞成 Map 好点哈。 /** * 赠送的优惠劵编号的数组 * diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java index ee6cb90b0..5cb932e4e 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java @@ -201,7 +201,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()); order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum)); order.setUserIp(getClientIP()).setTerminal(getTerminal()); - // 优惠券 + // 使用 + 赠送优惠券 order.setCouponIds(calculateRespBO.getCouponIds()).setCouponCounts(calculateRespBO.getCouponCounts()); // 支付 + 退款信息 order.setAdjustPrice(0).setPayStatus(false); @@ -861,17 +861,17 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @Override @Transactional(rollbackFor = Exception.class) public void cancelPaidOrder(Long userId, Long orderId, Integer cancelType) { - // 1. 这里校验下 cancelType 只允许拼团关闭; - if (!TradeOrderCancelTypeEnum.COMBINATION_CLOSE.getType().equals(cancelType)) { + // 1.1 这里校验下 cancelType 只允许拼团关闭; + if (ObjUtil.notEqual(TradeOrderCancelTypeEnum.COMBINATION_CLOSE.getType(), cancelType)) { return; } - // 1.1 检验订单存在 + // 1.2 检验订单存在 TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(orderId, userId); if (order == null) { throw exception(ORDER_NOT_FOUND); } - // 1.2 校验订单是否支付 + // 1.3 校验订单是否支付 if (!order.getPayStatus()) { throw exception(ORDER_CANCEL_PAID_FAIL, "已支付"); } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java index 6da931b6e..9428c6412 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java @@ -51,7 +51,7 @@ public class TradeCouponOrderHandler implements TradeOrderHandler { if (CollUtil.isEmpty(order.getCouponIds())) { return; } - // TODO @puhui999: 收回优惠券再考虑一下,是直接删除券还是改个状态 + // TODO @puhui999: 收回优惠券再考虑一下,是直接删除券还是改个状态;建议是【已作废】 } } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java index 119b68ec8..95f85aea8 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java @@ -72,6 +72,7 @@ public class TradePriceCalculateRespBO { */ private Boolean freeDelivery; + // TODO @puhui999:感觉要不要试着改成 Map giveCoupons?貌似整体会更好理解一点。 /** * 赠送的优惠劵编号的数组 */ diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java index d0dcf9cfd..8c0829f9a 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.module.member.api.address.MemberAddressApi; import cn.iocoder.yudao.module.member.api.address.dto.MemberAddressRespDTO; import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO; @@ -56,7 +57,7 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { return; } // 校验是不是存在商品不能门店自提,或者不能快递发货的情况。就是说,配送方式不匹配哈 - if (anyMatch(result.getItems(), item -> !item.getDeliveryTypes().contains(param.getDeliveryType()))) { + if (CollectionUtils.anyMatch(result.getItems(), item -> !item.getDeliveryTypes().contains(param.getDeliveryType()))) { throw exception(PRICE_CALCULATE_DELIVERY_PRICE_TYPE_ILLEGAL); } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java index 47420e24e..d543dc1f4 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java @@ -25,6 +25,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; import static cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper.formatPrice; +// TODO @puhui999:相关的单测,建议改一改 /** * 满减送活动的 {@link TradePriceCalculator} 实现类 * @@ -94,8 +95,8 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator if (rule.getGivePoint()) { List dividePoints = TradePriceCalculatorHelper.dividePrice(orderItems, rule.getPoint()); for (int i = 0; i < orderItems.size(); i++) { - TradePriceCalculateRespBO.OrderItem orderItem = orderItems.get(i); // 商品可能赠送了积分,所以这里要加上 + TradePriceCalculateRespBO.OrderItem orderItem = orderItems.get(i); orderItem.setGivePoint(orderItem.getGivePoint() + dividePoints.get(i)); } } @@ -189,7 +190,7 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator Integer price = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems); assert count != null && price != null; - // 2. 构建不满足时的提示信息-按最低档规则算 + // 2. 构建不满足时的提示信息:按最低档规则算 String meetTip = "满减送:购满 {} {},可以减 {} 元"; List rules = new ArrayList<>(rewardActivity.getRules()); rules.sort(Comparator.comparing(RewardActivityMatchRespDTO.Rule::getLimit)); // 按优惠门槛降序 From 806c828bb50fb7077803b19a5b8a96325a279053 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Fri, 30 Aug 2024 14:58:22 +0800 Subject: [PATCH 27/42] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E5=95=86=E5=9F=8E:=20=E6=BB=A1=E5=87=8F?= =?UTF-8?q?=E9=80=81=E6=B4=BB=E5=8A=A8=E4=BC=98=E6=83=A0=E5=88=B8=E7=9B=B8?= =?UTF-8?q?=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../promotion/api/coupon/CouponApi.java | 16 ++-- .../dto/RewardActivityMatchRespDTO.java | 11 ++- .../enums/coupon/CouponStatusEnum.java | 2 +- .../promotion/api/coupon/CouponApiImpl.java | 11 ++- .../admin/reward/vo/RewardActivityBaseVO.java | 12 +-- .../dal/dataobject/coupon/CouponDO.java | 1 - .../dataobject/reward/RewardActivityDO.java | 11 ++- .../dal/mysql/coupon/CouponMapper.java | 9 +++ .../service/coupon/CouponService.java | 13 +++- .../service/coupon/CouponServiceImpl.java | 74 ++++++++++++++----- .../dal/dataobject/order/TradeOrderDO.java | 17 ++--- .../order/TradeOrderUpdateServiceImpl.java | 2 +- .../handler/TradeCouponOrderHandler.java | 8 +- .../price/bo/TradePriceCalculateRespBO.java | 13 ++-- .../TradePriceCalculatorHelper.java | 4 +- .../TradeRewardActivityPriceCalculator.java | 21 +++--- 16 files changed, 136 insertions(+), 89 deletions(-) diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java index f7b741ddb..bda835678 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java @@ -5,7 +5,7 @@ import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO; import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO; import jakarta.validation.Valid; -import java.util.List; +import java.util.Map; /** * 优惠劵 API 接口 @@ -36,14 +36,20 @@ public interface CouponApi { */ CouponRespDTO validateCoupon(@Valid CouponValidReqDTO validReqDTO); - // TODO @puhui999:Map 优惠劵 会不会好点。 /** * 【管理员】给指定用户批量发送优惠券 * - * @param templateIds 优惠劵编号的数组 - * @param counts 优惠券数量的数组 + * @param giveCouponsMap key: 优惠劵编号,value:对应的优惠券数量 * @param userId 用户编号 */ - void takeCouponsByAdmin(List templateIds, List counts, Long userId); + void takeCouponsByAdmin(Map giveCouponsMap, Long userId); + + /** + * 【管理员】收回给指定用户批量发送优惠券 + * + * @param giveCouponsMap key: 优惠劵编号,value:对应的优惠券数量 + * @param userId 用户编号 + */ + void takeBackCouponsByAdmin(Map giveCouponsMap, Long userId); } diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java index a174637af..9cdb922f1 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java @@ -8,6 +8,7 @@ import lombok.Data; import java.io.Serializable; import java.time.LocalDateTime; import java.util.List; +import java.util.Map; /** * 满减送活动的匹配 Response DTO @@ -98,13 +99,11 @@ public class RewardActivityMatchRespDTO { */ private Boolean giveCoupon; /** - * 赠送的优惠劵编号的数组 + * 赠送的优惠劵 + * + * key: 优惠劵编号,value:对应的优惠券数量 */ - private List couponIds; - /** - * 赠送的优惠券数量的数组 - */ - private List couponCounts; + private Map giveCouponsMap; } diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/coupon/CouponStatusEnum.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/coupon/CouponStatusEnum.java index 320345d85..3edb3897f 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/coupon/CouponStatusEnum.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/coupon/CouponStatusEnum.java @@ -18,7 +18,7 @@ public enum CouponStatusEnum implements IntArrayValuable { UNUSED(1, "未使用"), USED(2, "已使用"), EXPIRE(3, "已过期"), - ; + INVALID(4, "已作废"); public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CouponStatusEnum::getStatus).toArray(); diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java index 23d088a74..b4778d0fe 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java @@ -11,7 +11,7 @@ import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; -import java.util.List; +import java.util.Map; /** * 优惠劵 API 实现类 @@ -43,8 +43,13 @@ public class CouponApiImpl implements CouponApi { } @Override - public void takeCouponsByAdmin(List templateIds, List counts, Long userId) { - couponService.takeCouponsByAdmin(templateIds, counts, userId); + public void takeCouponsByAdmin(Map giveCouponsMap, Long userId) { + couponService.takeCouponsByAdmin(giveCouponsMap, userId); + } + + @Override + public void takeBackCouponsByAdmin(Map giveCouponsMap, Long userId) { + couponService.takeBackCouponsByAdmin(giveCouponsMap, userId); } } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java index f932a58d6..0ed4b7d52 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java @@ -16,6 +16,7 @@ import lombok.Data; import java.time.LocalDateTime; import java.util.List; +import java.util.Map; import java.util.Objects; /** @@ -88,16 +89,7 @@ public class RewardActivityBaseVO { private Boolean giveCoupon; @Schema(description = "赠送的优惠劵编号的数组", example = "1,2,3") - private List couponIds; - - @Schema(description = "赠送的优惠券数量的数组", example = "1,2,3") - private List couponCounts; - - @AssertTrue(message = "优惠劵和数量必须一一对应") - @JsonIgnore - public boolean isCouponCountsValid() { - return BooleanUtil.isFalse(giveCoupon) || CollUtil.size(couponIds) == CollUtil.size(couponCounts); - } + private Map giveCouponsMap; @AssertTrue(message = "赠送的积分不能小于 1") @JsonIgnore diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/coupon/CouponDO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/coupon/CouponDO.java index 296d2a2fd..7182f0ea0 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/coupon/CouponDO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/coupon/CouponDO.java @@ -50,7 +50,6 @@ public class CouponDO extends BaseDO { * * 枚举 {@link CouponStatusEnum} */ - // TODO 芋艿:已作废? private Integer status; // TODO 芋艿:发放 adminid? diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java index 9a7135063..b1332cb3f 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java @@ -16,6 +16,7 @@ import lombok.EqualsAndHashCode; import java.io.Serializable; import java.time.LocalDateTime; import java.util.List; +import java.util.Map; /** * 满减送活动 DO @@ -114,13 +115,11 @@ public class RewardActivityDO extends BaseDO { */ private Boolean giveCoupon; /** - * 赠送的优惠劵编号的数组 + * 赠送的优惠劵 + * + * key: 优惠劵编号,value:对应的优惠券数量 */ - private List couponIds; - /** - * 赠送的优惠券数量的数组 - */ - private List couponCounts; + private Map giveCouponsMap; } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java index e5f1daf6c..913b84510 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java @@ -72,6 +72,15 @@ public interface CouponMapper extends BaseMapperX { ); } + default List selectListByTemplateIdAndUserIdAndTakeType(Long templateId, Collection userIds, + Integer takeType) { + return selectList(new LambdaQueryWrapperX() + .eq(CouponDO::getTemplateId, templateId) + .eq(CouponDO::getTakeType, takeType) + .in(CouponDO::getUserId, userIds) + ); + } + default Map selectCountByUserIdAndTemplateIdIn(Long userId, Collection templateIds) { String templateIdAlias = "templateId"; String countAlias = "count"; diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java index 5220a6da7..628a42e7f 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java @@ -108,11 +108,18 @@ public interface CouponService { /** * 【管理员】给指定用户批量发送优惠券 * - * @param templateIds 优惠劵编号的数组 - * @param counts 优惠券数量的数组 + * @param giveCouponsMap key: 优惠劵编号,value:对应的优惠券数量 * @param userId 用户编号 */ - void takeCouponsByAdmin(List templateIds, List counts, Long userId); + void takeCouponsByAdmin(Map giveCouponsMap, Long userId); + + /** + * 【管理员】收回给指定用户批量发送优惠券 + * + * @param giveCouponsMap key: 优惠劵编号,value:对应的优惠券数量 + * @param userId 用户编号 + */ + void takeBackCouponsByAdmin(Map giveCouponsMap, Long userId); /** * 【会员】领取优惠券 diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java index 222843c31..aff7579de 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java @@ -31,7 +31,6 @@ import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; -import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*; import static java.util.Arrays.asList; @@ -165,6 +164,7 @@ public class CouponServiceImpl implements CouponService { } @Override + @Transactional(rollbackFor = Exception.class) public void takeCoupon(Long templateId, Set userIds, CouponTakeTypeEnum takeType) { CouponTemplateDO template = couponTemplateService.getCouponTemplate(templateId); // 1. 过滤掉达到领取限制的用户 @@ -180,28 +180,66 @@ public class CouponServiceImpl implements CouponService { } @Override - public void takeCouponsByAdmin(List templateIds, List counts, Long userId) { - // TODO @puhui999:要不要循环调用上面的 takeCoupon 方法?按道理说,赠送也不会很多张。如果某次发卷失败,可以打个 error log; - // 1. 获得优惠券模版 - List templateList = couponTemplateService.getCouponTemplateList(templateIds); - if (CollUtil.isEmpty(templateList)) { + public void takeCouponsByAdmin(Map giveCouponsMap, Long userId) { + if (CollUtil.isEmpty(giveCouponsMap)) { return; } - Map templateMap = convertMap(templateList, CouponTemplateDO::getId); - // 2.1 批量构建优惠券 - List couponList = new ArrayList<>(); - for (int i = 0; i < templateIds.size(); i++) { - int finalI = i; - findAndThen(templateMap, templateIds.get(i), template -> { - for (int j = 0; j < counts.get(finalI); j++) { - couponList.add(CouponConvert.INSTANCE.convert(template, userId) - .setTakeType(CouponTakeTypeEnum.ADMIN.getValue())); + // 循环发放 + for (Map.Entry entry : giveCouponsMap.entrySet()) { + try { + for (int i = 0; i < entry.getValue(); i++) { + getSelf().takeCoupon(entry.getKey(), CollUtil.newHashSet(userId), CouponTakeTypeEnum.ADMIN); } - }); + } catch (Exception e) { + log.error("[takeCouponsByAdmin][coupon({}) 优惠券发放失败]", entry, e); + } } - // 2.2 批量保存优惠券 - couponMapper.insertBatch(couponList); + } + + @Override + public void takeBackCouponsByAdmin(Map giveCouponsMap, Long userId) { + // 循环收回 + for (Map.Entry entry : giveCouponsMap.entrySet()) { + try { + for (int i = 0; i < entry.getValue(); i++) { + getSelf().takeBackCoupon(entry.getKey(), CollUtil.newHashSet(userId), CouponTakeTypeEnum.ADMIN); + } + } catch (Exception e) { + log.error("[takeBackCouponsByAdmin][coupon({}) 收回优惠券失败]", entry, e); + } + } + } + + /** + * 【管理员】收回优惠券 + * + * @param templateId 模版编号 + * @param userIds 用户编号列表 + * @param takeType 领取方式 + */ + @Transactional(rollbackFor = Exception.class) + public void takeBackCoupon(Long templateId, Set userIds, CouponTakeTypeEnum takeType) { + CouponTemplateDO couponTemplate = couponTemplateService.getCouponTemplate(templateId); + // 1.1 校验模板 + if (couponTemplate == null) { + throw exception(COUPON_TEMPLATE_NOT_EXISTS); + } + // 1.2 校验领取方式 + if (ObjectUtil.notEqual(couponTemplate.getTakeType(), takeType.getValue())) { + throw exception(COUPON_TEMPLATE_CANNOT_TAKE); + } + + // 2.1 过滤出还未使用的赠送的优惠券 + List couponList = couponMapper.selectListByTemplateIdAndUserIdAndTakeType(templateId, userIds, + takeType.getValue()); + List unUsedCouponList = filterList(couponList, item -> !CouponStatusEnum.USED.getStatus().equals(item.getStatus())); + // 2.2 减少优惠劵模板的领取数量 + couponTemplateService.updateCouponTemplateTakeCount(templateId, unUsedCouponList.size() * -1); + // 2.3 批量更新优惠劵状态 + couponMapper.updateById(convertList(unUsedCouponList, item -> new CouponDO().setId(item.getId()) + .setStatus(CouponStatusEnum.INVALID.getStatus()))); + } @Override diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java index 4ce025408..710d8dc3f 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java @@ -12,11 +12,13 @@ import cn.iocoder.yudao.module.trade.enums.order.TradeOrderRefundStatusEnum; import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum; import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import lombok.*; import java.time.LocalDateTime; -import java.util.List; +import java.util.Map; /** * 交易订单 DO @@ -292,19 +294,14 @@ public class TradeOrderDO extends BaseDO { */ private Integer vipPrice; - // TODO @puhui999::1)建议命名要 giveXXX;不然不好理解哈;2)是不是搞成 Map 好点哈。 /** - * 赠送的优惠劵编号的数组 + * 赠送的优惠劵 * + * key: 优惠劵编号,value:对应的优惠券数量 * 目的:用于后续取消或者售后订单时,需要扣减赠送 */ - private List couponIds; - /** - * 赠送的优惠券数量的数组 - * - * 目的:用于后续取消或者售后订单时,需要扣减赠送 - */ - private List couponCounts; + @TableField(typeHandler = JacksonTypeHandler.class) + private Map giveCouponsMap; /** * 秒杀活动编号 diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java index 5cb932e4e..c9c1e685b 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java @@ -202,7 +202,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum)); order.setUserIp(getClientIP()).setTerminal(getTerminal()); // 使用 + 赠送优惠券 - order.setCouponIds(calculateRespBO.getCouponIds()).setCouponCounts(calculateRespBO.getCouponCounts()); + order.setGiveCouponsMap(calculateRespBO.getGiveCouponsMap()); // 支付 + 退款信息 order.setAdjustPrice(0).setPayStatus(false); order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()).setRefundPrice(0); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java index 9428c6412..e364bc007 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java @@ -33,11 +33,11 @@ public class TradeCouponOrderHandler implements TradeOrderHandler { @Override public void afterPayOrder(TradeOrderDO order, List orderItems) { - if (CollUtil.isEmpty(order.getCouponIds())) { + if (CollUtil.isEmpty(order.getGiveCouponsMap())) { return; } // 赠送优惠券 - couponApi.takeCouponsByAdmin(order.getCouponIds(), order.getCouponCounts(), order.getUserId()); + couponApi.takeCouponsByAdmin(order.getGiveCouponsMap(), order.getUserId()); } @Override @@ -48,10 +48,10 @@ public class TradeCouponOrderHandler implements TradeOrderHandler { couponApi.returnUsedCoupon(order.getCouponId()); } // 情况二:收回赠送的优惠券 - if (CollUtil.isEmpty(order.getCouponIds())) { + if (CollUtil.isEmpty(order.getGiveCouponsMap())) { return; } - // TODO @puhui999: 收回优惠券再考虑一下,是直接删除券还是改个状态;建议是【已作废】 + couponApi.takeBackCouponsByAdmin(order.getGiveCouponsMap(), order.getUserId()); } } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java index 95f85aea8..e53613d26 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; import lombok.Data; import java.util.List; +import java.util.Map; /** * 价格计算 Response BO @@ -72,15 +73,13 @@ public class TradePriceCalculateRespBO { */ private Boolean freeDelivery; - // TODO @puhui999:感觉要不要试着改成 Map giveCoupons?貌似整体会更好理解一点。 /** - * 赠送的优惠劵编号的数组 + * 赠送的优惠劵 + * + * key: 优惠劵编号,value:对应的优惠券数量 + * 目的:用于后续取消或者售后订单时,需要扣减赠送 */ - private List couponIds; - /** - * 赠送的优惠券数量的数组 - */ - private List couponCounts; + private Map giveCouponsMap; /** * 订单价格 diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java index cb8f97c11..6fa639c5a 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java @@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -31,8 +32,7 @@ public class TradePriceCalculatorHelper { List spuList, List skuList) { // 创建 PriceCalculateRespDTO 对象 TradePriceCalculateRespBO result = new TradePriceCalculateRespBO(); - result.setType(getOrderType(param)).setPromotions(new ArrayList<>()) - .setCouponIds(new ArrayList<>()).setCouponCounts(new ArrayList<>()); + result.setType(getOrderType(param)).setPromotions(new ArrayList<>()).setGiveCouponsMap(new LinkedHashMap<>()); // 创建它的 OrderItem 属性 result.setItems(new ArrayList<>(param.getItems().size())); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java index d543dc1f4..05679d836 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java @@ -19,13 +19,14 @@ import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.Comparator; import java.util.List; -import java.util.Objects; +import java.util.Map; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; import static cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper.formatPrice; // TODO @puhui999:相关的单测,建议改一改 + /** * 满减送活动的 {@link TradePriceCalculator} 实现类 * @@ -107,16 +108,12 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator } // 4.3 记录赠送的优惠券 if (rule.getGiveCoupon()) { - for (int i = 0; i < rule.getCouponIds().size(); i++) { - Long couponId = result.getCouponIds().get(i); - Integer couponCount = result.getCouponCounts().get(i); - int index = CollUtil.indexOf(result.getCouponIds(), id -> Objects.equals(couponId, id)); - if (index != -1) { // 情况一:别的满减活动送过同类优惠券,则直接增加数量 - List couponCounts = result.getCouponCounts(); - couponCounts.set(index, couponCounts.get(index) + couponCount); - result.setCouponCounts(couponCounts); - } else { // 情况二:还没有赠送的优惠券 - result.setCouponIds(rule.getCouponIds()).setCouponCounts(rule.getCouponCounts()); + for (Map.Entry entry : rule.getGiveCouponsMap().entrySet()) { + Map giveCouponsMap = result.getGiveCouponsMap(); + if (giveCouponsMap.get(entry.getKey()) == null) { // 情况一:还没有赠送的优惠券 + result.setGiveCouponsMap(rule.getGiveCouponsMap()); + } else { // 情况二:别的满减活动送过同类优惠券,则直接增加数量 + giveCouponsMap.put(entry.getKey(), giveCouponsMap.get(entry.getKey()) + entry.getValue()); } } } @@ -193,7 +190,7 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator // 2. 构建不满足时的提示信息:按最低档规则算 String meetTip = "满减送:购满 {} {},可以减 {} 元"; List rules = new ArrayList<>(rewardActivity.getRules()); - rules.sort(Comparator.comparing(RewardActivityMatchRespDTO.Rule::getLimit)); // 按优惠门槛降序 + rules.sort(Comparator.comparing(RewardActivityMatchRespDTO.Rule::getLimit)); // 按优惠门槛升序 RewardActivityMatchRespDTO.Rule rule = rules.get(0); if (PromotionConditionTypeEnum.PRICE.getType().equals(rewardActivity.getConditionType())) { return StrUtil.format(meetTip, rule.getLimit(), "元", MoneyUtils.fenToYuanStr(rule.getDiscountPrice())); From d26ef6b89e2c3748360a92255ffbee61822c6429 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Fri, 30 Aug 2024 16:17:19 +0800 Subject: [PATCH 28/42] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E5=95=86=E5=9F=8E:=20TradeRewardActivityPric?= =?UTF-8?q?eCalculatorTest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TradeRewardActivityPriceCalculator.java | 6 ++--- ...radeRewardActivityPriceCalculatorTest.java | 27 +++++++++++++------ .../src/test/resources/sql/create_tables.sql | 1 + 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java index 05679d836..6b333df47 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java @@ -93,7 +93,7 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator TradePriceCalculatorHelper.recountAllPrice(result); // 4.1 记录赠送的积分 - if (rule.getGivePoint()) { + if (Boolean.TRUE.equals(rule.getGivePoint())) { List dividePoints = TradePriceCalculatorHelper.dividePrice(orderItems, rule.getPoint()); for (int i = 0; i < orderItems.size(); i++) { // 商品可能赠送了积分,所以这里要加上 @@ -102,12 +102,12 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator } } // 4.2 记录订单是否包邮 - if (rule.getFreeDelivery()) { + if (Boolean.TRUE.equals(rule.getFreeDelivery())) { // 只要满足一个活动包邮那么这单就包邮 result.setFreeDelivery(true); } // 4.3 记录赠送的优惠券 - if (rule.getGiveCoupon()) { + if (Boolean.TRUE.equals(rule.getGiveCoupon())) { for (Map.Entry entry : rule.getGiveCouponsMap().entrySet()) { Map giveCouponsMap = result.getGiveCouponsMap(); if (giveCouponsMap.get(entry.getKey()) == null) { // 情况一:还没有赠送的优惠券 diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java index 219ae727e..3ae34514d 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; import cn.iocoder.yudao.module.promotion.api.reward.RewardActivityApi; import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO; import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum; +import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum; import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; @@ -13,6 +14,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import java.util.ArrayList; +import java.util.LinkedHashMap; import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; @@ -47,7 +49,7 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest TradePriceCalculateRespBO result = new TradePriceCalculateRespBO() .setType(TradeOrderTypeEnum.NORMAL.getType()) .setPrice(new TradePriceCalculateRespBO.Price()) - .setPromotions(new ArrayList<>()) + .setPromotions(new ArrayList<>()).setGiveCouponsMap(new LinkedHashMap<>()) .setItems(asList( new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true) .setPrice(100).setSpuId(1L), @@ -60,16 +62,22 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest TradePriceCalculatorHelper.recountPayPrice(result.getItems()); TradePriceCalculatorHelper.recountAllPrice(result); - // mock 方法(限时折扣 DiscountActivity 信息) + // mock 方法(满减送 RewardActivity 信息) when(rewardActivityApi.getMatchRewardActivityList(eq(asSet(1L, 2L, 3L)))).thenReturn(asList( randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(1000L).setName("活动 1000 号") - .setProductScopeValues(asList(1L, 2L)).setConditionType(PromotionConditionTypeEnum.PRICE.getType()) - .setRules(singletonList(new RewardActivityMatchRespDTO.Rule().setLimit(200).setDiscountPrice(70)))), + .setConditionType(PromotionConditionTypeEnum.PRICE.getType()) + .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(asList(1L, 2L)) + .setRules(singletonList(new RewardActivityMatchRespDTO.Rule().setLimit(20).setDiscountPrice(70) + .setGivePoint(false).setFreeDelivery(false)))), randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(2000L).setName("活动 2000 号") - .setProductScopeValues(singletonList(3L)).setConditionType(PromotionConditionTypeEnum.COUNT.getType()) - .setRules(asList(new RewardActivityMatchRespDTO.Rule().setLimit(1).setDiscountPrice(10), - new RewardActivityMatchRespDTO.Rule().setLimit(2).setDiscountPrice(60), // 最大可满足,因为是 4 个 - new RewardActivityMatchRespDTO.Rule().setLimit(10).setDiscountPrice(100)))) + .setConditionType(PromotionConditionTypeEnum.COUNT.getType()) + .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(singletonList(3L)) + .setRules(asList(new RewardActivityMatchRespDTO.Rule().setLimit(1).setDiscountPrice(10) + .setGivePoint(true).setPoint(50).setFreeDelivery(false), + new RewardActivityMatchRespDTO.Rule().setLimit(2).setDiscountPrice(60).setGivePoint(true) + .setPoint(100).setFreeDelivery(false), // 最大可满足,因为是 4 个 + new RewardActivityMatchRespDTO.Rule().setLimit(10).setDiscountPrice(100) + .setGivePoint(false).setFreeDelivery(false)))) )); // 调用 @@ -94,6 +102,7 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest assertEquals(orderItem01.getCouponPrice(), 0); assertEquals(orderItem01.getPointPrice(), 0); assertEquals(orderItem01.getPayPrice(), 160); + assertEquals(orderItem01.getGivePoint(), 0); // 断言:SKU 2 TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1); assertEquals(orderItem02.getSkuId(), 20L); @@ -104,6 +113,7 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest assertEquals(orderItem02.getCouponPrice(), 0); assertEquals(orderItem02.getPointPrice(), 0); assertEquals(orderItem02.getPayPrice(), 120); + assertEquals(orderItem02.getGivePoint(), 0); // 断言:SKU 3 TradePriceCalculateRespBO.OrderItem orderItem03 = result.getItems().get(2); assertEquals(orderItem03.getSkuId(), 30L); @@ -114,6 +124,7 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest assertEquals(orderItem03.getCouponPrice(), 0); assertEquals(orderItem03.getPointPrice(), 0); assertEquals(orderItem03.getPayPrice(), 60); + assertEquals(orderItem03.getGivePoint(), 100); // 断言:Promotion 部分(第一个) assertEquals(result.getPromotions().size(), 2); TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/create_tables.sql b/yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/create_tables.sql index f619c01de..1d7ed24ee 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/create_tables.sql +++ b/yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/create_tables.sql @@ -48,6 +48,7 @@ CREATE TABLE IF NOT EXISTS "trade_order" "give_point" int NULL, "refund_point" int NULL, "vip_price" int NULL, + "give_coupons_map" varchar NULL, "seckill_activity_id" long NULL, "bargain_activity_id" long NULL, "bargain_record_id" long NULL, From 88cc4c987b26c2e9ebb37da4c944932f3ab729b6 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Fri, 30 Aug 2024 21:37:51 +0800 Subject: [PATCH 29/42] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91=E5=95=86=E5=9F=8E=EF=BC=9A=E6=BB=A1=E5=87=8F?= =?UTF-8?q?=E9=80=81=E8=AE=A2=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/module/promotion/api/coupon/CouponApi.java | 7 +++++-- .../api/reward/dto/RewardActivityMatchRespDTO.java | 5 ++++- .../module/promotion/enums/coupon/CouponStatusEnum.java | 1 + .../yudao/module/promotion/api/coupon/CouponApiImpl.java | 4 ++-- .../module/promotion/service/coupon/CouponService.java | 2 +- .../module/promotion/service/coupon/CouponServiceImpl.java | 2 +- .../module/trade/dal/dataobject/order/TradeOrderDO.java | 5 ++++- .../service/order/handler/TradeCouponOrderHandler.java | 2 +- 8 files changed, 19 insertions(+), 9 deletions(-) diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java index bda835678..c724df8c1 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java @@ -36,20 +36,23 @@ public interface CouponApi { */ CouponRespDTO validateCoupon(@Valid CouponValidReqDTO validReqDTO); + // TODO @puhui999:可能需要根据 TradeOrderDO 的建议,进行修改;需要返回优惠劵编号 /** * 【管理员】给指定用户批量发送优惠券 * * @param giveCouponsMap key: 优惠劵编号,value:对应的优惠券数量 * @param userId 用户编号 */ + // TODO @puhui999:giveCouponsMap 可能改成 giveCoupons 更合适?优惠劵模版编号、数量 void takeCouponsByAdmin(Map giveCouponsMap, Long userId); + // TODO @puhui999:可能需要根据 TradeOrderDO 的建议,进行修改 giveCouponsMap 参数 /** - * 【管理员】收回给指定用户批量发送优惠券 + * 【管理员】作废指定用户的指定优惠劵 * * @param giveCouponsMap key: 优惠劵编号,value:对应的优惠券数量 * @param userId 用户编号 */ - void takeBackCouponsByAdmin(Map giveCouponsMap, Long userId); + void invalidateCouponsByAdmin(Map giveCouponsMap, Long userId); } diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java index 9cdb922f1..93b5691fb 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java @@ -86,6 +86,7 @@ public class RewardActivityMatchRespDTO { * 是否包邮 */ private Boolean freeDelivery; + // TODO @puhui999:建议不返回 + 去掉 givePoint、giveCoupon 字段哈。 /** * 是否赠送积分 */ @@ -98,10 +99,12 @@ public class RewardActivityMatchRespDTO { * 是否赠送优惠券 */ private Boolean giveCoupon; + // TODO @puhui999:giveCoupons 即可 /** * 赠送的优惠劵 * - * key: 优惠劵编号,value:对应的优惠券数量 + * key: 优惠劵模版编号 + * value:对应的优惠券数量 */ private Map giveCouponsMap; diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/coupon/CouponStatusEnum.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/coupon/CouponStatusEnum.java index 3edb3897f..831d4b5a0 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/coupon/CouponStatusEnum.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/coupon/CouponStatusEnum.java @@ -18,6 +18,7 @@ public enum CouponStatusEnum implements IntArrayValuable { UNUSED(1, "未使用"), USED(2, "已使用"), EXPIRE(3, "已过期"), + // TODO @puhui999:捉摸了下,貌似搞成逻辑删除好了?不然好多地方的 status 都要做一些变动。可能未来加个 invalidateType 来标识,是管理后台删除,还是取消回收。或者优惠劵的 change log 可能更好。 INVALID(4, "已作废"); public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CouponStatusEnum::getStatus).toArray(); diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java index b4778d0fe..22fea4525 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java @@ -48,8 +48,8 @@ public class CouponApiImpl implements CouponApi { } @Override - public void takeBackCouponsByAdmin(Map giveCouponsMap, Long userId) { - couponService.takeBackCouponsByAdmin(giveCouponsMap, userId); + public void invalidateCouponsByAdmin(Map giveCouponsMap, Long userId) { + couponService.invalidateCouponsByAdmin(giveCouponsMap, userId); } } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java index 628a42e7f..97c1412ca 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java @@ -119,7 +119,7 @@ public interface CouponService { * @param giveCouponsMap key: 优惠劵编号,value:对应的优惠券数量 * @param userId 用户编号 */ - void takeBackCouponsByAdmin(Map giveCouponsMap, Long userId); + void invalidateCouponsByAdmin(Map giveCouponsMap, Long userId); /** * 【会员】领取优惠券 diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java index aff7579de..666a310e7 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java @@ -198,7 +198,7 @@ public class CouponServiceImpl implements CouponService { } @Override - public void takeBackCouponsByAdmin(Map giveCouponsMap, Long userId) { + public void invalidateCouponsByAdmin(Map giveCouponsMap, Long userId) { // 循环收回 for (Map.Entry entry : giveCouponsMap.entrySet()) { try { diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java index 710d8dc3f..82b6d6117 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java @@ -294,10 +294,13 @@ public class TradeOrderDO extends BaseDO { */ private Integer vipPrice; + // TODO @puhui999:项了下,貌似这里存储 List giveCouponIds 更合适。因为优惠劵赠送到最后是对应的编号,然后从而进行取消? /** * 赠送的优惠劵 * - * key: 优惠劵编号,value:对应的优惠券数量 + * key: 优惠劵编号 + * value:对应的优惠券数量 + * * 目的:用于后续取消或者售后订单时,需要扣减赠送 */ @TableField(typeHandler = JacksonTypeHandler.class) diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java index e364bc007..3b1df5e0e 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java @@ -51,7 +51,7 @@ public class TradeCouponOrderHandler implements TradeOrderHandler { if (CollUtil.isEmpty(order.getGiveCouponsMap())) { return; } - couponApi.takeBackCouponsByAdmin(order.getGiveCouponsMap(), order.getUserId()); + couponApi.invalidateCouponsByAdmin(order.getGiveCouponsMap(), order.getUserId()); } } From d6ecc032c2ec342522622afe358383eee81f2230 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 31 Aug 2024 08:53:05 +0800 Subject: [PATCH 30/42] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91SYSTEM=EF=BC=9A=E4=B8=83=E7=89=9B=E4=BA=91?= =?UTF-8?q?=E7=9F=AD=E4=BF=A1=E7=9A=84=E6=8E=A5=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sms/core/client/impl/QiniuSmsClient.java | 92 +++++++++---------- .../client/impl/SmsClientFactoryImpl.java | 1 + .../core/client/impl/TencentSmsClient.java | 2 +- .../core/client/impl/QiniuSmsClientTest.java | 20 ++-- .../sms/core/client/impl/SmsClientTests.java | 9 +- 5 files changed, 60 insertions(+), 64 deletions(-) diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClient.java index 4fbb8649d..a041970be 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClient.java @@ -1,11 +1,9 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; import cn.hutool.core.collection.CollStreamUtil; -import cn.hutool.core.collection.ListUtil; import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.digest.HmacAlgorithm; @@ -22,6 +20,9 @@ import com.google.common.annotations.VisibleForTesting; import lombok.extern.slf4j.Slf4j; import java.util.*; +import java.util.function.Function; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; /** * 七牛云短信客户端的实现类 @@ -34,19 +35,12 @@ public class QiniuSmsClient extends AbstractSmsClient { private static final String HOST = "sms.qiniuapi.com"; - private static final String PATH = "/v1/message/single"; - - private static final String TEMPLATE_PATH = "/v1/template"; - public QiniuSmsClient(SmsChannelProperties properties) { super(properties); Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空"); Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); } - protected void doInit() { - } - public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, List> templateParams) throws Throwable { // 1. 执行请求 @@ -56,16 +50,16 @@ public class QiniuSmsClient extends AbstractSmsClient { body.put("mobile", mobile); body.put("parameters", CollStreamUtil.toMap(templateParams, KeyValue::getKey, KeyValue::getValue)); body.put("seq", Long.toString(sendLogId)); + JSONObject response = request("POST", body, "/v1/message/single"); - JSONObject response = request("POST", body, PATH); // 2. 解析请求 - if (ObjectUtil.isNotEmpty(response.getStr("error"))){//短信请求失败 + if (ObjectUtil.isNotEmpty(response.getStr("error"))) { + // 短信请求失败 return new SmsSendRespDTO().setSuccess(false) .setApiCode(response.getStr("error")) .setApiRequestId(response.getStr("request_id")) .setApiMsg(response.getStr("message")); } - return new SmsSendRespDTO().setSuccess(response.containsKey("message_id")) .setSerialNo(response.getStr("message_id")); } @@ -81,62 +75,66 @@ public class QiniuSmsClient extends AbstractSmsClient { */ private JSONObject request(String httpMethod, LinkedHashMap body, String path) { String signDate = DateUtil.date().setTimeZone(TimeZone.getTimeZone("UTC")).toString("yyyyMMdd'T'HHmmss'Z'"); - //请求头 + // 1. 请求头 Map header = new HashMap<>(4); header.put("HOST", HOST); - header.put("Authorization", getSignature(httpMethod, HOST, path, body != null ? JSONUtil.toJsonStr(body) : "", signDate)); + header.put("Authorization", getSignature(httpMethod, path, body != null ? JSONUtil.toJsonStr(body) : "", signDate)); header.put("Content-Type", "application/json"); header.put("X-Qiniu-Date", signDate); - String responseBody =""; - if (Objects.equals(httpMethod, "POST")){// POST 发送短消息用POST请求 + // 2. 发起请求 + String responseBody; + if (Objects.equals(httpMethod, "POST")){ responseBody = HttpUtils.post("https://" + HOST + path, header, JSONUtil.toJsonStr(body)); - }else { // GET 查询template状态用GET请求 + } else { responseBody = HttpUtils.get("https://" + HOST + path, header); } return JSONUtil.parseObj(responseBody); } - public String getSignature(String method, String host, String path, String body, String signDate) { + private String getSignature(String method, String path, String body, String signDate) { StringBuilder dataToSign = new StringBuilder(); - dataToSign.append(method.toUpperCase()).append(" ").append(path); - dataToSign.append("\nHost: ").append(host); - dataToSign.append("\n").append("Content-Type").append(": ").append("application/json"); - dataToSign.append("\n").append("X-Qiniu-Date").append(": ").append(signDate); - dataToSign.append("\n\n"); + dataToSign.append(method.toUpperCase()).append(" ").append(path) + .append("\nHost: ").append(HOST) + .append("\n").append("Content-Type").append(": ").append("application/json") + .append("\n").append("X-Qiniu-Date").append(": ").append(signDate) + .append("\n\n"); if (ObjectUtil.isNotEmpty(body)) { dataToSign.append(body); } - String encodedSignature = SecureUtil.hmac(HmacAlgorithm.HmacSHA1, properties.getApiSecret()).digestBase64(dataToSign.toString(), true); - - return "Qiniu " + properties.getApiKey() + ":" + encodedSignature; + String signature = SecureUtil.hmac(HmacAlgorithm.HmacSHA1, properties.getApiSecret()) + .digestBase64(dataToSign.toString(), true); + return "Qiniu " + properties.getApiKey() + ":" + signature; } @Override public List parseSmsReceiveStatus(String text) { JSONObject status = JSONUtil.parseObj(text); // 字段参考 https://developer.qiniu.com/sms/5910/message-push - return ListUtil.of(new SmsReceiveRespDTO() - .setSuccess("DELIVRD".equals(status.getJSONArray("items").getJSONObject(0).getStr("status"))) // 是否接收成功 - .setErrorMsg(status.getJSONArray("items").getJSONObject(0).getStr("status")) - .setMobile(status.getJSONArray("items").getJSONObject(0).getStr("mobile")) // 手机号 - .setReceiveTime(LocalDateTimeUtil.of(status.getJSONArray("items").getJSONObject(0).getLong("delivrd_at")*1000L)) - .setSerialNo(status.getJSONArray("items").getJSONObject(0).getStr("message_id")) // 发送序列号 - .setLogId(Long.valueOf(status.getJSONArray("items").getJSONObject(0).getStr("seq")))); // logId + return convertList(status.getJSONArray("items"), new Function() { + + @Override + public SmsReceiveRespDTO apply(Object item) { + JSONObject statusObj = (JSONObject) item; + return new SmsReceiveRespDTO() + .setSuccess("DELIVRD".equals(statusObj.getStr("status"))) // 是否接收成功 + .setErrorMsg(statusObj.getStr("status")) // 状态报告编码 + .setMobile(statusObj.getStr("mobile")) // 手机号 + .setReceiveTime(LocalDateTimeUtil.of(statusObj.getLong("delivrd_at") * 1000L)) // 状态报告时间 + .setSerialNo(statusObj.getStr("message_id")) // 发送序列号 + .setLogId(statusObj.getLong("seq")); // 用户序列号 + } + + }); } @Override public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable { // 1. 执行请求 // 参考链接 https://developer.qiniu.com/sms/5969/query-a-single-template - JSONObject response = request("GET", null, TEMPLATE_PATH + "/" + apiTemplateId); - // 2.1 请求失败 - if (ObjUtil.notEqual(response.getStr("audit_status"), "passed")) { - log.error("[getSmsTemplate][模版编号({}) 响应不正确({})]", apiTemplateId, response); - return null; - } + JSONObject response = request("GET", null, "/v1/template/" + apiTemplateId); - // 2.2 请求成功 + // 2.2 解析请求 return new SmsTemplateRespDTO() .setId(response.getStr("id")) .setContent(response.getStr("template")) @@ -146,12 +144,12 @@ public class QiniuSmsClient extends AbstractSmsClient { @VisibleForTesting Integer convertSmsTemplateAuditStatus(String templateStatus) { - return switch (templateStatus) { - case "passed" -> SmsTemplateAuditStatusEnum.SUCCESS.getStatus(); - case "reviewing" -> SmsTemplateAuditStatusEnum.CHECKING.getStatus(); - case "rejected" -> SmsTemplateAuditStatusEnum.FAIL.getStatus(); - case null, default -> - throw new IllegalArgumentException(String.format("未知审核状态(%str)", templateStatus)); - }; + switch (templateStatus) { + case "passed": return SmsTemplateAuditStatusEnum.SUCCESS.getStatus(); + case "reviewing": return SmsTemplateAuditStatusEnum.CHECKING.getStatus(); + case "rejected": return SmsTemplateAuditStatusEnum.FAIL.getStatus(); + default: + throw new IllegalArgumentException(String.format("未知审核状态(%str)", templateStatus)); + } } } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientFactoryImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientFactoryImpl.java index dde1475d4..da783189b 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientFactoryImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientFactoryImpl.java @@ -80,6 +80,7 @@ public class SmsClientFactoryImpl implements SmsClientFactory { case DEBUG_DING_TALK: return new DebugDingTalkSmsClient(properties); case TENCENT: return new TencentSmsClient(properties); case HUAWEI: return new HuaweiSmsClient(properties); + case QINIU: return new QiniuSmsClient(properties); } // 创建失败,错误日志 + 抛出异常 log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", properties); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java index ae3138362..19cde8c26 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java @@ -162,7 +162,7 @@ public class TencentSmsClient extends AbstractSmsClient { * @param body 请求参数 * @return 请求结果 */ - private JSONObject request(String action, TreeMap body) throws Exception { + private JSONObject request(String action, TreeMap body) { // 1.1 请求 Header Map headers = new HashMap<>(); headers.put("Content-Type", "application/json; charset=utf-8"); diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClientTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClientTest.java index c3e896695..93b99bcc5 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClientTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClientTest.java @@ -29,6 +29,7 @@ import static org.mockito.Mockito.mockStatic; * @author scholar */ public class QiniuSmsClientTest extends BaseMockitoUnitTest { + private final SmsChannelProperties properties = new SmsChannelProperties() .setApiKey(randomString())// 随机一个 apiKey,避免构建报错 .setApiSecret(randomString()) // 随机一个 apiSecret,避免构建报错 @@ -37,12 +38,6 @@ public class QiniuSmsClientTest extends BaseMockitoUnitTest { @InjectMocks private QiniuSmsClient smsClient = new QiniuSmsClient(properties); - @Test - public void testDoInit() { - // 调用 - smsClient.doInit(); - } - @Test public void testDoSendSms_success() throws Throwable { try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { @@ -113,12 +108,13 @@ public class QiniuSmsClientTest extends BaseMockitoUnitTest { List statuses = smsClient.parseSmsReceiveStatus(text); // 断言 assertEquals(1, statuses.size()); - assertTrue(statuses.getFirst().getSuccess()); - assertEquals("DELIVRD", statuses.getFirst().getErrorMsg()); - assertEquals(LocalDateTime.of(2024, 8, 25, 21, 14, 26), statuses.getFirst().getReceiveTime()); - assertEquals("18881234567", statuses.getFirst().getMobile()); - assertEquals("10135515063508004167", statuses.getFirst().getSerialNo()); - assertEquals(123, statuses.getFirst().getLogId()); + SmsReceiveRespDTO status = statuses.get(0); + assertTrue(status.getSuccess()); + assertEquals("DELIVRD", status.getErrorMsg()); + assertEquals(LocalDateTime.of(2024, 8, 25, 21, 14, 26), status.getReceiveTime()); + assertEquals("18881234567", status.getMobile()); + assertEquals("10135515063508004167", status.getSerialNo()); + assertEquals(123, status.getLogId()); } @Test diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java index 09608fea0..faba754a2 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; +import cn.hutool.core.collection.ListUtil; import cn.iocoder.yudao.framework.common.core.KeyValue; import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient; import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO; @@ -47,7 +48,7 @@ public class SmsClientTests { String mobile = "15601691323"; String apiTemplateId = "SMS_207945135"; // 调用 - SmsSendRespDTO sendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, List.of(new KeyValue<>("code", "1024"))); + SmsSendRespDTO sendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, ListUtil.of(new KeyValue<>("code", "1024"))); // 打印结果 System.out.println(sendRespDTO); } @@ -68,7 +69,7 @@ public class SmsClientTests { String mobile = "15601691323"; String apiTemplateId = "358212"; // 调用 - SmsSendRespDTO sendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, List.of(new KeyValue<>("code", "1024"))); + SmsSendRespDTO sendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, ListUtil.of(new KeyValue<>("code", "1024"))); // 打印结果 System.out.println(sendRespDTO); } @@ -105,7 +106,7 @@ public class SmsClientTests { Long sendLogId = System.currentTimeMillis(); String mobile = "17321315478"; String apiTemplateId = "3644cdab863546a3b718d488659a99ef"; - List> templateParams = List.of(new KeyValue<>("code", "1024")); + List> templateParams = ListUtil.of(new KeyValue<>("code", "1024")); // 调用 SmsSendRespDTO smsSendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, templateParams); // 打印结果 @@ -125,7 +126,7 @@ public class SmsClientTests { Long sendLogId = System.currentTimeMillis(); String mobile = "17321315478"; String apiTemplateId = "3644cdab863546a3b718d488659a99ef"; - List> templateParams = List.of(new KeyValue<>("code", "1122")); + List> templateParams = ListUtil.of(new KeyValue<>("code", "1122")); // 调用 SmsSendRespDTO smsSendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, templateParams); // 打印结果 From 5ea3e5db0da5c4a4bb7c9c9dc9f6bf5c92f25a22 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Mon, 2 Sep 2024 11:23:13 +0800 Subject: [PATCH 31/42] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E5=95=86=E5=9F=8E:=20=E6=BB=A1=E5=87=8F?= =?UTF-8?q?=E9=80=81=E6=B4=BB=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../promotion/api/coupon/CouponApi.java | 13 +- .../dto/RewardActivityMatchRespDTO.java | 12 +- .../enums/coupon/CouponStatusEnum.java | 4 +- .../promotion/api/coupon/CouponApiImpl.java | 9 +- .../dal/mysql/coupon/CouponMapper.java | 17 +- .../service/coupon/CouponService.java | 103 +++++---- .../service/coupon/CouponServiceImpl.java | 212 ++++++++++-------- .../dal/dataobject/order/TradeOrderDO.java | 12 +- .../order/TradeOrderUpdateService.java | 11 + .../order/TradeOrderUpdateServiceImpl.java | 18 +- .../handler/TradeCouponOrderHandler.java | 19 +- .../price/bo/TradePriceCalculateRespBO.java | 2 +- .../TradePriceCalculatorHelper.java | 2 +- .../TradeRewardActivityPriceCalculator.java | 14 +- ...radeRewardActivityPriceCalculatorTest.java | 10 +- 15 files changed, 256 insertions(+), 202 deletions(-) diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java index c724df8c1..789a4526d 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java @@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO; import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO; import jakarta.validation.Valid; +import java.util.List; import java.util.Map; /** @@ -36,23 +37,21 @@ public interface CouponApi { */ CouponRespDTO validateCoupon(@Valid CouponValidReqDTO validReqDTO); - // TODO @puhui999:可能需要根据 TradeOrderDO 的建议,进行修改;需要返回优惠劵编号 /** * 【管理员】给指定用户批量发送优惠券 * - * @param giveCouponsMap key: 优惠劵编号,value:对应的优惠券数量 + * @param giveCoupons key: 优惠劵模版编号,value:对应的数量 * @param userId 用户编号 + * @return 优惠券编号列表 */ - // TODO @puhui999:giveCouponsMap 可能改成 giveCoupons 更合适?优惠劵模版编号、数量 - void takeCouponsByAdmin(Map giveCouponsMap, Long userId); + List takeCouponsByAdmin(Map giveCoupons, Long userId); - // TODO @puhui999:可能需要根据 TradeOrderDO 的建议,进行修改 giveCouponsMap 参数 /** * 【管理员】作废指定用户的指定优惠劵 * - * @param giveCouponsMap key: 优惠劵编号,value:对应的优惠券数量 + * @param giveCouponIds 赠送的优惠券编号 * @param userId 用户编号 */ - void invalidateCouponsByAdmin(Map giveCouponsMap, Long userId); + void invalidateCouponsByAdmin(List giveCouponIds, Long userId); } diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java index 93b5691fb..d8d5ef135 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java @@ -86,27 +86,17 @@ public class RewardActivityMatchRespDTO { * 是否包邮 */ private Boolean freeDelivery; - // TODO @puhui999:建议不返回 + 去掉 givePoint、giveCoupon 字段哈。 - /** - * 是否赠送积分 - */ - private Boolean givePoint; /** * 赠送的积分 */ private Integer point; - /** - * 是否赠送优惠券 - */ - private Boolean giveCoupon; - // TODO @puhui999:giveCoupons 即可 /** * 赠送的优惠劵 * * key: 优惠劵模版编号 * value:对应的优惠券数量 */ - private Map giveCouponsMap; + private Map giveCoupons; } diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/coupon/CouponStatusEnum.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/coupon/CouponStatusEnum.java index 831d4b5a0..bef4db225 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/coupon/CouponStatusEnum.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/coupon/CouponStatusEnum.java @@ -17,9 +17,7 @@ public enum CouponStatusEnum implements IntArrayValuable { UNUSED(1, "未使用"), USED(2, "已使用"), - EXPIRE(3, "已过期"), - // TODO @puhui999:捉摸了下,貌似搞成逻辑删除好了?不然好多地方的 status 都要做一些变动。可能未来加个 invalidateType 来标识,是管理后台删除,还是取消回收。或者优惠劵的 change log 可能更好。 - INVALID(4, "已作废"); + EXPIRE(3, "已过期"); public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CouponStatusEnum::getStatus).toArray(); diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java index 22fea4525..edc8f1b7f 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java @@ -11,6 +11,7 @@ import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; +import java.util.List; import java.util.Map; /** @@ -43,13 +44,13 @@ public class CouponApiImpl implements CouponApi { } @Override - public void takeCouponsByAdmin(Map giveCouponsMap, Long userId) { - couponService.takeCouponsByAdmin(giveCouponsMap, userId); + public List takeCouponsByAdmin(Map giveCoupons, Long userId) { + return couponService.takeCouponsByAdmin(giveCoupons, userId); } @Override - public void invalidateCouponsByAdmin(Map giveCouponsMap, Long userId) { - couponService.invalidateCouponsByAdmin(giveCouponsMap, userId); + public void invalidateCouponsByAdmin(List giveCouponIds, Long userId) { + couponService.invalidateCouponsByAdmin(giveCouponIds, userId); } } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java index 913b84510..a06b92338 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java @@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO; import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.github.yulichang.toolkit.MPJWrappers; import org.apache.ibatis.annotations.Mapper; @@ -72,15 +73,6 @@ public interface CouponMapper extends BaseMapperX { ); } - default List selectListByTemplateIdAndUserIdAndTakeType(Long templateId, Collection userIds, - Integer takeType) { - return selectList(new LambdaQueryWrapperX() - .eq(CouponDO::getTemplateId, templateId) - .eq(CouponDO::getTakeType, takeType) - .in(CouponDO::getUserId, userIds) - ); - } - default Map selectCountByUserIdAndTemplateIdIn(Long userId, Collection templateIds) { String templateIdAlias = "templateId"; String countAlias = "count"; @@ -116,4 +108,11 @@ public interface CouponMapper extends BaseMapperX { ); } + default List selectListByIdAndUserIdAndTakeType(Long couponId, Long userId, Integer takeType) { + return selectList(new LambdaQueryWrapper() + .eq(CouponDO::getId, couponId) + .eq(CouponDO::getUserId, userId) + .eq(CouponDO::getTakeType, takeType)); + } + } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java index 97c1412ca..622b09a5b 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java @@ -38,14 +38,6 @@ public interface CouponService { */ void validCoupon(CouponDO coupon); - /** - * 获得优惠劵分页 - * - * @param pageReqVO 分页查询 - * @return 优惠劵分页 - */ - PageResult getCouponPage(CouponPageReqVO pageReqVO); - /** * 使用优惠劵 * @@ -69,57 +61,43 @@ public interface CouponService { */ void deleteCoupon(Long id); - /** - * 获得用户的优惠劵列表 - * - * @param userId 用户编号 - * @param status 优惠劵状态 - * @return 优惠劵列表 - */ - List getCouponList(Long userId, Integer status); - - /** - * 获得未使用的优惠劵数量 - * - * @param userId 用户编号 - * @return 未使用的优惠劵数量 - */ - Long getUnusedCouponCount(Long userId); - /** * 领取优惠券 * * @param templateId 优惠券模板编号 * @param userIds 用户编号列表 * @param takeType 领取方式 + * @return key: userId, value: 优惠券编号列表 */ - void takeCoupon(Long templateId, Set userIds, CouponTakeTypeEnum takeType); + Map> takeCoupon(Long templateId, Set userIds, CouponTakeTypeEnum takeType); /** * 【管理员】给用户发送优惠券 * * @param templateId 优惠券模板编号 * @param userIds 用户编号列表 + * @return key: userId, value: 优惠券编号列表 */ - default void takeCouponByAdmin(Long templateId, Set userIds) { - takeCoupon(templateId, userIds, CouponTakeTypeEnum.ADMIN); + default Map> takeCouponByAdmin(Long templateId, Set userIds) { + return takeCoupon(templateId, userIds, CouponTakeTypeEnum.ADMIN); } /** * 【管理员】给指定用户批量发送优惠券 * - * @param giveCouponsMap key: 优惠劵编号,value:对应的优惠券数量 + * @param giveCoupons key: 优惠劵模版编号,value:对应的数量 * @param userId 用户编号 + * @return 优惠券编号列表 */ - void takeCouponsByAdmin(Map giveCouponsMap, Long userId); + List takeCouponsByAdmin(Map giveCoupons, Long userId); /** - * 【管理员】收回给指定用户批量发送优惠券 + * 【管理员】作废指定用户的指定优惠劵 * - * @param giveCouponsMap key: 优惠劵编号,value:对应的优惠券数量 + * @param giveCouponIds 赠送的优惠券编号 * @param userId 用户编号 */ - void invalidateCouponsByAdmin(Map giveCouponsMap, Long userId); + void invalidateCouponsByAdmin(List giveCouponIds, Long userId); /** * 【会员】领取优惠券 @@ -138,6 +116,49 @@ public interface CouponService { */ void takeCouponByRegister(Long userId); + /** + * 过期优惠券 + * + * @return 过期数量 + */ + int expireCoupon(); + + //======================= 查询相关 ======================= + + /** + * 获得未使用的优惠劵数量 + * + * @param userId 用户编号 + * @return 未使用的优惠劵数量 + */ + Long getUnusedCouponCount(Long userId); + + /** + * 获得优惠劵分页 + * + * @param pageReqVO 分页查询 + * @return 优惠劵分页 + */ + PageResult getCouponPage(CouponPageReqVO pageReqVO); + + /** + * 获得用户的优惠劵列表 + * + * @param userId 用户编号 + * @param status 优惠劵状态 + * @return 优惠劵列表 + */ + List getCouponList(Long userId, Integer status); + + /** + * 统计会员领取优惠券的数量 + * + * @param templateIds 优惠券模板编号列表 + * @param userId 用户编号 + * @return 领取优惠券的数量 + */ + Map getTakeCountMapByTemplateIds(Collection templateIds, Long userId); + /** * 获取会员领取指定优惠券的数量 * @@ -150,15 +171,6 @@ public interface CouponService { return MapUtil.getInt(map, templateId, 0); } - /** - * 统计会员领取优惠券的数量 - * - * @param templateIds 优惠券模板编号列表 - * @param userId 用户编号 - * @return 领取优惠券的数量 - */ - Map getTakeCountMapByTemplateIds(Collection templateIds, Long userId); - /** * 获取用户匹配的优惠券列表 * @@ -168,13 +180,6 @@ public interface CouponService { */ List getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO); - /** - * 过期优惠券 - * - * @return 过期数量 - */ - int expireCoupon(); - /** * 获取用户是否可以领取优惠券 * diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java index 666a310e7..ecc1adb46 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.promotion.service.coupon; import cn.hutool.core.collection.CollStreamUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.spring.SpringUtil; @@ -31,6 +32,7 @@ import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*; import static java.util.Arrays.asList; @@ -75,20 +77,6 @@ public class CouponServiceImpl implements CouponService { } } - @Override - public PageResult getCouponPage(CouponPageReqVO pageReqVO) { - // 获得用户编号 - if (StrUtil.isNotEmpty(pageReqVO.getNickname())) { - List users = memberUserApi.getUserListByNickname(pageReqVO.getNickname()); - if (CollUtil.isEmpty(users)) { - return PageResult.empty(); - } - pageReqVO.setUserIds(convertSet(users, MemberUserRespDTO::getId)); - } - // 分页查询 - return couponMapper.selectPage(pageReqVO); - } - @Override public void useCoupon(Long id, Long userId, Long orderId) { // 校验优惠劵 @@ -145,27 +133,9 @@ public class CouponServiceImpl implements CouponService { couponTemplateService.updateCouponTemplateTakeCount(coupon.getTemplateId(), -1); } - @Override - public List getCouponList(Long userId, Integer status) { - return couponMapper.selectListByUserIdAndStatus(userId, status); - } - - private CouponDO validateCouponExists(Long id) { - CouponDO coupon = couponMapper.selectById(id); - if (coupon == null) { - throw exception(COUPON_NOT_EXISTS); - } - return coupon; - } - - @Override - public Long getUnusedCouponCount(Long userId) { - return couponMapper.selectCountByUserIdAndStatus(userId, CouponStatusEnum.UNUSED.getStatus()); - } - @Override @Transactional(rollbackFor = Exception.class) - public void takeCoupon(Long templateId, Set userIds, CouponTakeTypeEnum takeType) { + public Map> takeCoupon(Long templateId, Set userIds, CouponTakeTypeEnum takeType) { CouponTemplateDO template = couponTemplateService.getCouponTemplate(templateId); // 1. 过滤掉达到领取限制的用户 removeTakeLimitUser(userIds, template); @@ -173,40 +143,45 @@ public class CouponServiceImpl implements CouponService { validateCouponTemplateCanTake(template, userIds, takeType); // 3. 批量保存优惠劵 - couponMapper.insertBatch(convertList(userIds, userId -> CouponConvert.INSTANCE.convert(template, userId))); + List couponList = convertList(userIds, userId -> CouponConvert.INSTANCE.convert(template, userId)); + couponMapper.insertBatch(couponList); // 4. 增加优惠劵模板的领取数量 couponTemplateService.updateCouponTemplateTakeCount(templateId, userIds.size()); + + return convertMultiMap(couponList, CouponDO::getUserId, CouponDO::getId); } @Override - public void takeCouponsByAdmin(Map giveCouponsMap, Long userId) { - if (CollUtil.isEmpty(giveCouponsMap)) { - return; + public List takeCouponsByAdmin(Map giveCoupons, Long userId) { + if (CollUtil.isEmpty(giveCoupons)) { + return Collections.emptyList(); } + List couponIds = new ArrayList<>(); // 循环发放 - for (Map.Entry entry : giveCouponsMap.entrySet()) { + for (Map.Entry entry : giveCoupons.entrySet()) { try { for (int i = 0; i < entry.getValue(); i++) { - getSelf().takeCoupon(entry.getKey(), CollUtil.newHashSet(userId), CouponTakeTypeEnum.ADMIN); + Map> userCouponIdsMap = getSelf().takeCoupon(entry.getKey(), CollUtil.newHashSet(userId), + CouponTakeTypeEnum.ADMIN); + findAndThen(userCouponIdsMap, userId, couponIds::addAll); } } catch (Exception e) { log.error("[takeCouponsByAdmin][coupon({}) 优惠券发放失败]", entry, e); } } + return couponIds; } @Override - public void invalidateCouponsByAdmin(Map giveCouponsMap, Long userId) { + public void invalidateCouponsByAdmin(List giveCouponIds, Long userId) { // 循环收回 - for (Map.Entry entry : giveCouponsMap.entrySet()) { + for (Long couponId : giveCouponIds) { try { - for (int i = 0; i < entry.getValue(); i++) { - getSelf().takeBackCoupon(entry.getKey(), CollUtil.newHashSet(userId), CouponTakeTypeEnum.ADMIN); - } + getSelf().takeBackCoupon(couponId, userId, CouponTakeTypeEnum.ADMIN); } catch (Exception e) { - log.error("[takeBackCouponsByAdmin][coupon({}) 收回优惠券失败]", entry, e); + log.error("[invalidateCouponsByAdmin][couponId({}) 收回优惠券失败]", couponId, e); } } } @@ -214,32 +189,36 @@ public class CouponServiceImpl implements CouponService { /** * 【管理员】收回优惠券 * - * @param templateId 模版编号 - * @param userIds 用户编号列表 + * @param couponId 模版编号 + * @param userId 用户编号 * @param takeType 领取方式 */ @Transactional(rollbackFor = Exception.class) - public void takeBackCoupon(Long templateId, Set userIds, CouponTakeTypeEnum takeType) { - CouponTemplateDO couponTemplate = couponTemplateService.getCouponTemplate(templateId); - // 1.1 校验模板 + public void takeBackCoupon(Long couponId, Long userId, CouponTakeTypeEnum takeType) { + // 1.1 校验优惠券 + CouponDO coupon = couponMapper.selectByIdAndUserId(couponId, userId); + if (coupon == null) { + throw exception(COUPON_NOT_EXISTS); + } + // 1.2 校验模板 + CouponTemplateDO couponTemplate = couponTemplateService.getCouponTemplate(coupon.getTemplateId()); if (couponTemplate == null) { throw exception(COUPON_TEMPLATE_NOT_EXISTS); } - // 1.2 校验领取方式 + // 1.3 校验领取方式 if (ObjectUtil.notEqual(couponTemplate.getTakeType(), takeType.getValue())) { throw exception(COUPON_TEMPLATE_CANNOT_TAKE); } - // 2.1 过滤出还未使用的赠送的优惠券 - List couponList = couponMapper.selectListByTemplateIdAndUserIdAndTakeType(templateId, userIds, - takeType.getValue()); - List unUsedCouponList = filterList(couponList, item -> !CouponStatusEnum.USED.getStatus().equals(item.getStatus())); + // 2.1 校验优惠券是否已经使用,如若使用则先不管 + if (ObjUtil.equal(coupon.getStatus(), CouponStatusEnum.USED.getStatus())) { + return; + } // 2.2 减少优惠劵模板的领取数量 - couponTemplateService.updateCouponTemplateTakeCount(templateId, unUsedCouponList.size() * -1); - // 2.3 批量更新优惠劵状态 - couponMapper.updateById(convertList(unUsedCouponList, item -> new CouponDO().setId(item.getId()) - .setStatus(CouponStatusEnum.INVALID.getStatus()))); - + couponTemplateService.updateCouponTemplateTakeCount(couponTemplate.getId(), -1); + // 2.3 批量作废优惠劵 + // TODO @puhui999:捉摸了下,貌似搞成逻辑删除好了?不然好多地方的 status 都要做一些变动。可能未来加个 invalidateType 来标识,是管理后台删除,还是取消回收。或者优惠劵的 change log 可能更好。 + couponMapper.deleteById(couponId); } @Override @@ -251,24 +230,6 @@ public class CouponServiceImpl implements CouponService { } } - @Override - public Map getTakeCountMapByTemplateIds(Collection templateIds, Long userId) { - if (CollUtil.isEmpty(templateIds)) { - return Collections.emptyMap(); - } - return couponMapper.selectCountByUserIdAndTemplateIdIn(userId, templateIds); - } - - @Override - public List getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO) { - List list = couponMapper.selectListByUserIdAndStatusAndUsePriceLeAndProductScope(userId, - CouponStatusEnum.UNUSED.getStatus(), - matchReqVO.getPrice(), matchReqVO.getSpuIds(), matchReqVO.getCategoryIds()); - // 兜底逻辑:如果 CouponExpireJob 未执行,status 未变成 EXPIRE ,但是 validEndTime 已经过期了,需要进行过滤 - list.removeIf(coupon -> !LocalDateTimeUtils.isBetween(coupon.getValidStartTime(), coupon.getValidEndTime())); - return list; - } - @Override public int expireCoupon() { // 1. 查询待过期的优惠券 @@ -293,27 +254,6 @@ public class CouponServiceImpl implements CouponService { return count; } - @Override - public Map getUserCanCanTakeMap(Long userId, List templates) { - // 1. 未登录时,都显示可以领取 - Map userCanTakeMap = convertMap(templates, CouponTemplateDO::getId, templateId -> true); - if (userId == null) { - return userCanTakeMap; - } - - // 2.1 过滤领取数量无限制的 - Set templateIds = convertSet(templates, CouponTemplateDO::getId, template -> template.getTakeLimitCount() != -1); - // 2.2 检查用户领取的数量是否超过限制 - if (CollUtil.isNotEmpty(templateIds)) { - Map couponTakeCountMap = this.getTakeCountMapByTemplateIds(templateIds, userId); - for (CouponTemplateDO template : templates) { - Integer takeCount = couponTakeCountMap.get(template.getId()); - userCanTakeMap.put(template.getId(), takeCount == null || takeCount < template.getTakeLimitCount()); - } - } - return userCanTakeMap; - } - /** * 过期单个优惠劵 * @@ -385,11 +325,84 @@ public class CouponServiceImpl implements CouponService { userIds.removeIf(userId -> MapUtil.getInt(userTakeCountMap, userId, 0) >= couponTemplate.getTakeLimitCount()); } + //======================= 查询相关 ======================= + + @Override + public Long getUnusedCouponCount(Long userId) { + return couponMapper.selectCountByUserIdAndStatus(userId, CouponStatusEnum.UNUSED.getStatus()); + } + + @Override + public PageResult getCouponPage(CouponPageReqVO pageReqVO) { + // 获得用户编号 + if (StrUtil.isNotEmpty(pageReqVO.getNickname())) { + List users = memberUserApi.getUserListByNickname(pageReqVO.getNickname()); + if (CollUtil.isEmpty(users)) { + return PageResult.empty(); + } + pageReqVO.setUserIds(convertSet(users, MemberUserRespDTO::getId)); + } + // 分页查询 + return couponMapper.selectPage(pageReqVO); + } + + @Override + public List getCouponList(Long userId, Integer status) { + return couponMapper.selectListByUserIdAndStatus(userId, status); + } + + @Override + public Map getTakeCountMapByTemplateIds(Collection templateIds, Long userId) { + if (CollUtil.isEmpty(templateIds)) { + return Collections.emptyMap(); + } + return couponMapper.selectCountByUserIdAndTemplateIdIn(userId, templateIds); + } + + @Override + public List getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO) { + List list = couponMapper.selectListByUserIdAndStatusAndUsePriceLeAndProductScope(userId, + CouponStatusEnum.UNUSED.getStatus(), + matchReqVO.getPrice(), matchReqVO.getSpuIds(), matchReqVO.getCategoryIds()); + // 兜底逻辑:如果 CouponExpireJob 未执行,status 未变成 EXPIRE ,但是 validEndTime 已经过期了,需要进行过滤 + list.removeIf(coupon -> !LocalDateTimeUtils.isBetween(coupon.getValidStartTime(), coupon.getValidEndTime())); + return list; + } + + @Override + public Map getUserCanCanTakeMap(Long userId, List templates) { + // 1. 未登录时,都显示可以领取 + Map userCanTakeMap = convertMap(templates, CouponTemplateDO::getId, templateId -> true); + if (userId == null) { + return userCanTakeMap; + } + + // 2.1 过滤领取数量无限制的 + Set templateIds = convertSet(templates, CouponTemplateDO::getId, template -> template.getTakeLimitCount() != -1); + // 2.2 检查用户领取的数量是否超过限制 + if (CollUtil.isNotEmpty(templateIds)) { + Map couponTakeCountMap = this.getTakeCountMapByTemplateIds(templateIds, userId); + for (CouponTemplateDO template : templates) { + Integer takeCount = couponTakeCountMap.get(template.getId()); + userCanTakeMap.put(template.getId(), takeCount == null || takeCount < template.getTakeLimitCount()); + } + } + return userCanTakeMap; + } + @Override public CouponDO getCoupon(Long userId, Long id) { return couponMapper.selectByIdAndUserId(id, userId); } + private CouponDO validateCouponExists(Long id) { + CouponDO coupon = couponMapper.selectById(id); + if (coupon == null) { + throw exception(COUPON_NOT_EXISTS); + } + return coupon; + } + /** * 获得自身的代理对象,解决 AOP 生效问题 * @@ -398,4 +411,5 @@ public class CouponServiceImpl implements CouponService { private CouponServiceImpl getSelf() { return SpringUtil.getBean(getClass()); } + } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java index 82b6d6117..1409561d5 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.trade.dal.dataobject.order; import cn.iocoder.yudao.framework.common.enums.TerminalEnum; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler; import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO; import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO; @@ -18,6 +19,7 @@ import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import lombok.*; import java.time.LocalDateTime; +import java.util.List; import java.util.Map; /** @@ -294,17 +296,23 @@ public class TradeOrderDO extends BaseDO { */ private Integer vipPrice; - // TODO @puhui999:项了下,貌似这里存储 List giveCouponIds 更合适。因为优惠劵赠送到最后是对应的编号,然后从而进行取消? /** * 赠送的优惠劵 * * key: 优惠劵编号 * value:对应的优惠券数量 * - * 目的:用于后续取消或者售后订单时,需要扣减赠送 + * 目的:用于订单支付后赠送优惠券 */ @TableField(typeHandler = JacksonTypeHandler.class) private Map giveCouponsMap; + /** + * 赠送的优惠劵编号 + * + * 目的:用于后续取消或者售后订单时,需要扣减赠送 + */ + @TableField(typeHandler = LongListTypeHandler.class) + private List giveCouponIds; /** * 秒杀活动编号 diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java index 4508138ff..56b7cbc56 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java @@ -11,6 +11,8 @@ import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderI import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; import jakarta.validation.constraints.NotNull; +import java.util.List; + /** * 交易订单【写】Service 接口 * @@ -194,4 +196,13 @@ public interface TradeOrderUpdateService { */ void cancelPaidOrder(Long userId, Long orderId, Integer cancelType); + /** + * 更新下单赠送的优惠券编号到订单 + * + * @param userId 用户编号 + * @param orderId 订单编号 + * @param giveCouponIds 赠送的优惠券编号列表 + */ + void updateOrderGiveCouponIds(Long userId, Long orderId, List giveCouponIds); + } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java index c9c1e685b..bdae8f227 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java @@ -202,7 +202,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum)); order.setUserIp(getClientIP()).setTerminal(getTerminal()); // 使用 + 赠送优惠券 - order.setGiveCouponsMap(calculateRespBO.getGiveCouponsMap()); + order.setGiveCouponsMap(calculateRespBO.getGiveCoupons()); // 支付 + 退款信息 order.setAdjustPrice(0).setPayStatus(false); order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()).setRefundPrice(0); @@ -890,6 +890,22 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { .setReason(TradeOrderCancelTypeEnum.COMBINATION_CLOSE.getName()).setPrice(order.getPayPrice()));// 价格信息 } + @Override + public void updateOrderGiveCouponIds(Long userId, Long orderId, List giveCouponIds) { + // 1.1 检验订单存在 + TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(orderId, userId); + if (order == null) { + throw exception(ORDER_NOT_FOUND); + } + // 1.2 校验订单是否支付 + if (!order.getPayStatus()) { + throw exception(ORDER_CANCEL_PAID_FAIL, "已支付"); + } + + // 2. 更新订单赠送的优惠券编号列表 + tradeOrderMapper.updateById(new TradeOrderDO().setId(orderId).setGiveCouponIds(giveCouponIds)); + } + /** * 创建单个订单的评论 * diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java index 3b1df5e0e..3a98a6c9e 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java @@ -5,7 +5,10 @@ import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi; import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; +import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService; +import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService; import jakarta.annotation.Resource; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import java.util.List; @@ -18,6 +21,12 @@ import java.util.List; @Component public class TradeCouponOrderHandler implements TradeOrderHandler { + @Resource + @Lazy // 延迟加载,避免循环依赖 + private TradeOrderUpdateService orderUpdateService; + @Resource + private TradeOrderQueryService orderQueryService; + @Resource private CouponApi couponApi; @@ -37,7 +46,11 @@ public class TradeCouponOrderHandler implements TradeOrderHandler { return; } // 赠送优惠券 - couponApi.takeCouponsByAdmin(order.getGiveCouponsMap(), order.getUserId()); + List couponIds = couponApi.takeCouponsByAdmin(order.getGiveCouponsMap(), order.getUserId()); + if (CollUtil.isEmpty(couponIds)) { + return; + } + orderUpdateService.updateOrderGiveCouponIds(order.getUserId(), order.getId(), couponIds); } @Override @@ -48,10 +61,10 @@ public class TradeCouponOrderHandler implements TradeOrderHandler { couponApi.returnUsedCoupon(order.getCouponId()); } // 情况二:收回赠送的优惠券 - if (CollUtil.isEmpty(order.getGiveCouponsMap())) { + if (CollUtil.isEmpty(order.getGiveCouponIds())) { return; } - couponApi.invalidateCouponsByAdmin(order.getGiveCouponsMap(), order.getUserId()); + couponApi.invalidateCouponsByAdmin(order.getGiveCouponIds(), order.getUserId()); } } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java index e53613d26..68fa58b37 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java @@ -79,7 +79,7 @@ public class TradePriceCalculateRespBO { * key: 优惠劵编号,value:对应的优惠券数量 * 目的:用于后续取消或者售后订单时,需要扣减赠送 */ - private Map giveCouponsMap; + private Map giveCoupons; /** * 订单价格 diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java index 6fa639c5a..195ef8718 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java @@ -32,7 +32,7 @@ public class TradePriceCalculatorHelper { List spuList, List skuList) { // 创建 PriceCalculateRespDTO 对象 TradePriceCalculateRespBO result = new TradePriceCalculateRespBO(); - result.setType(getOrderType(param)).setPromotions(new ArrayList<>()).setGiveCouponsMap(new LinkedHashMap<>()); + result.setType(getOrderType(param)).setPromotions(new ArrayList<>()).setGiveCoupons(new LinkedHashMap<>()); // 创建它的 OrderItem 属性 result.setItems(new ArrayList<>(param.getItems().size())); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java index 6b333df47..f62b65eb9 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java @@ -93,7 +93,7 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator TradePriceCalculatorHelper.recountAllPrice(result); // 4.1 记录赠送的积分 - if (Boolean.TRUE.equals(rule.getGivePoint())) { + if (rule.getPoint() != null && rule.getPoint() > 0) { List dividePoints = TradePriceCalculatorHelper.dividePrice(orderItems, rule.getPoint()); for (int i = 0; i < orderItems.size(); i++) { // 商品可能赠送了积分,所以这里要加上 @@ -107,13 +107,13 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator result.setFreeDelivery(true); } // 4.3 记录赠送的优惠券 - if (Boolean.TRUE.equals(rule.getGiveCoupon())) { - for (Map.Entry entry : rule.getGiveCouponsMap().entrySet()) { - Map giveCouponsMap = result.getGiveCouponsMap(); - if (giveCouponsMap.get(entry.getKey()) == null) { // 情况一:还没有赠送的优惠券 - result.setGiveCouponsMap(rule.getGiveCouponsMap()); + if (CollUtil.isNotEmpty(rule.getGiveCoupons())) { + for (Map.Entry entry : rule.getGiveCoupons().entrySet()) { + Map giveCoupons = result.getGiveCoupons(); + if (giveCoupons.get(entry.getKey()) == null) { // 情况一:还没有赠送的优惠券 + result.setGiveCoupons(rule.getGiveCoupons()); } else { // 情况二:别的满减活动送过同类优惠券,则直接增加数量 - giveCouponsMap.put(entry.getKey(), giveCouponsMap.get(entry.getKey()) + entry.getValue()); + giveCoupons.put(entry.getKey(), giveCoupons.get(entry.getKey()) + entry.getValue()); } } } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java index 3ae34514d..f1f31e3c8 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java @@ -49,7 +49,7 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest TradePriceCalculateRespBO result = new TradePriceCalculateRespBO() .setType(TradeOrderTypeEnum.NORMAL.getType()) .setPrice(new TradePriceCalculateRespBO.Price()) - .setPromotions(new ArrayList<>()).setGiveCouponsMap(new LinkedHashMap<>()) + .setPromotions(new ArrayList<>()).setGiveCoupons(new LinkedHashMap<>()) .setItems(asList( new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true) .setPrice(100).setSpuId(1L), @@ -68,16 +68,16 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest .setConditionType(PromotionConditionTypeEnum.PRICE.getType()) .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(asList(1L, 2L)) .setRules(singletonList(new RewardActivityMatchRespDTO.Rule().setLimit(20).setDiscountPrice(70) - .setGivePoint(false).setFreeDelivery(false)))), + .setFreeDelivery(false)))), randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(2000L).setName("活动 2000 号") .setConditionType(PromotionConditionTypeEnum.COUNT.getType()) .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(singletonList(3L)) .setRules(asList(new RewardActivityMatchRespDTO.Rule().setLimit(1).setDiscountPrice(10) - .setGivePoint(true).setPoint(50).setFreeDelivery(false), - new RewardActivityMatchRespDTO.Rule().setLimit(2).setDiscountPrice(60).setGivePoint(true) + .setPoint(50).setFreeDelivery(false), + new RewardActivityMatchRespDTO.Rule().setLimit(2).setDiscountPrice(60) .setPoint(100).setFreeDelivery(false), // 最大可满足,因为是 4 个 new RewardActivityMatchRespDTO.Rule().setLimit(10).setDiscountPrice(100) - .setGivePoint(false).setFreeDelivery(false)))) + .setFreeDelivery(false)))) )); // 调用 From 81e38666659cea502f9d1c921fa5278f8d12764c Mon Sep 17 00:00:00 2001 From: puhui999 Date: Mon, 2 Sep 2024 11:35:33 +0800 Subject: [PATCH 32/42] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E5=95=86=E5=9F=8E:=20=E6=BB=A1=E5=87=8F?= =?UTF-8?q?=E9=80=81=E6=B4=BB=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/reward/vo/RewardActivityBaseVO.java | 11 +---------- .../dal/dataobject/reward/RewardActivityDO.java | 10 ---------- .../service/reward/RewardActivityServiceImpl.java | 1 - 3 files changed, 1 insertion(+), 21 deletions(-) diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java index 0ed4b7d52..7f68ee123 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java @@ -1,7 +1,6 @@ package cn.iocoder.yudao.module.promotion.controller.admin.reward.vo; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.BooleanUtil; import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum; import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; @@ -77,24 +76,16 @@ public class RewardActivityBaseVO { @NotNull(message = "规则是否包邮不能为空") private Boolean freeDelivery; - @Schema(description = "是否赠送积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") - @NotNull(message = "规则是否赠送积分不能为空") - private Boolean givePoint; - @Schema(description = "赠送的积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") private Integer point; - @Schema(description = "是否赠送优惠券", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") - @NotNull(message = "规则是否赠送优惠券不能为空") - private Boolean giveCoupon; - @Schema(description = "赠送的优惠劵编号的数组", example = "1,2,3") private Map giveCouponsMap; @AssertTrue(message = "赠送的积分不能小于 1") @JsonIgnore public boolean isPointValid() { - return BooleanUtil.isFalse(givePoint) || (point != null && point >= 1); + return point == null || point >= 1; } } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java index b1332cb3f..f5d60c4e6 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java @@ -100,20 +100,10 @@ public class RewardActivityDO extends BaseDO { * 是否包邮 */ private Boolean freeDelivery; - // TODO @puhui999:是不是大于零,就认为赠送积分哈;简洁一点; - /** - * 是否赠送积分 - */ - private Boolean givePoint; /** * 赠送的积分 */ private Integer point; - // TODO @puhui999:非空,就认为赠送优惠劵 - /** - * 是否赠送优惠券 - */ - private Boolean giveCoupon; /** * 赠送的优惠劵 * diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java index 1ad0ae48f..d35142c19 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java @@ -159,7 +159,6 @@ public class RewardActivityServiceImpl implements RewardActivityService { @Override public List getMatchRewardActivityList(Collection spuIds) { - // TODO 芋艿:待实现;先指定,然后再全局的; List list = rewardActivityMapper.selectListBySpuIdsAndStatus(spuIds, CommonStatusEnum.ENABLE.getStatus()); return BeanUtils.toBean(list, RewardActivityMatchRespDTO.class); } From eaeeb34e74c14d1b89c95c727bae8a2f64ca6aaa Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 2 Sep 2024 12:25:53 +0800 Subject: [PATCH 33/42] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91=E5=95=86=E5=9F=8E=EF=BC=9A=E6=BB=A1=E5=87=8F?= =?UTF-8?q?=E9=80=81=E8=AE=A2=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dal/mysql/coupon/CouponMapper.java | 8 -------- .../service/coupon/CouponService.java | 2 +- .../service/coupon/CouponServiceImpl.java | 19 +++++++------------ .../dal/dataobject/order/TradeOrderDO.java | 3 ++- .../order/TradeOrderUpdateServiceImpl.java | 6 +----- 5 files changed, 11 insertions(+), 27 deletions(-) diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java index a06b92338..e5f1daf6c 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java @@ -8,7 +8,6 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO; import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.github.yulichang.toolkit.MPJWrappers; import org.apache.ibatis.annotations.Mapper; @@ -108,11 +107,4 @@ public interface CouponMapper extends BaseMapperX { ); } - default List selectListByIdAndUserIdAndTakeType(Long couponId, Long userId, Integer takeType) { - return selectList(new LambdaQueryWrapper() - .eq(CouponDO::getId, couponId) - .eq(CouponDO::getUserId, userId) - .eq(CouponDO::getTakeType, takeType)); - } - } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java index 622b09a5b..5fdcd0669 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java @@ -123,7 +123,7 @@ public interface CouponService { */ int expireCoupon(); - //======================= 查询相关 ======================= + // ======================= 查询相关 ======================= /** * 获得未使用的优惠劵数量 diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java index ecc1adb46..e6cd4ba0e 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java @@ -179,7 +179,7 @@ public class CouponServiceImpl implements CouponService { // 循环收回 for (Long couponId : giveCouponIds) { try { - getSelf().takeBackCoupon(couponId, userId, CouponTakeTypeEnum.ADMIN); + getSelf().invalidateCoupon(couponId, userId); } catch (Exception e) { log.error("[invalidateCouponsByAdmin][couponId({}) 收回优惠券失败]", couponId, e); } @@ -191,10 +191,9 @@ public class CouponServiceImpl implements CouponService { * * @param couponId 模版编号 * @param userId 用户编号 - * @param takeType 领取方式 */ @Transactional(rollbackFor = Exception.class) - public void takeBackCoupon(Long couponId, Long userId, CouponTakeTypeEnum takeType) { + public void invalidateCoupon(Long couponId, Long userId) { // 1.1 校验优惠券 CouponDO coupon = couponMapper.selectByIdAndUserId(couponId, userId); if (coupon == null) { @@ -205,19 +204,15 @@ public class CouponServiceImpl implements CouponService { if (couponTemplate == null) { throw exception(COUPON_TEMPLATE_NOT_EXISTS); } - // 1.3 校验领取方式 - if (ObjectUtil.notEqual(couponTemplate.getTakeType(), takeType.getValue())) { - throw exception(COUPON_TEMPLATE_CANNOT_TAKE); - } - - // 2.1 校验优惠券是否已经使用,如若使用则先不管 + // 1.3 校验优惠券是否已经使用,如若使用则先不管 if (ObjUtil.equal(coupon.getStatus(), CouponStatusEnum.USED.getStatus())) { + log.info("[invalidateCoupon][coupon({}) 已经使用,无法作废]", couponId); return; } - // 2.2 减少优惠劵模板的领取数量 + + // 2.1 减少优惠劵模板的领取数量 couponTemplateService.updateCouponTemplateTakeCount(couponTemplate.getId(), -1); - // 2.3 批量作废优惠劵 - // TODO @puhui999:捉摸了下,貌似搞成逻辑删除好了?不然好多地方的 status 都要做一些变动。可能未来加个 invalidateType 来标识,是管理后台删除,还是取消回收。或者优惠劵的 change log 可能更好。 + // 2.2 作废优惠劵 couponMapper.deleteById(couponId); } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java index 1409561d5..4cfee5e17 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java @@ -296,10 +296,11 @@ public class TradeOrderDO extends BaseDO { */ private Integer vipPrice; + // TODO @puhui999:我们要不要把相关的字段,定义的更明确一点?例如说,giveCouponTemplateCounts 赠送的优惠劵模版数量,或者 giveCouponCounts 赠送的优惠劵数量。感受上,Coupons 和 Map 有点点重叠哈。 /** * 赠送的优惠劵 * - * key: 优惠劵编号 + * key: 优惠劵模版编号 * value:对应的优惠券数量 * * 目的:用于订单支付后赠送优惠券 diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java index bdae8f227..379be205f 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java @@ -892,15 +892,11 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @Override public void updateOrderGiveCouponIds(Long userId, Long orderId, List giveCouponIds) { - // 1.1 检验订单存在 + // 1. 检验订单存在 TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(orderId, userId); if (order == null) { throw exception(ORDER_NOT_FOUND); } - // 1.2 校验订单是否支付 - if (!order.getPayStatus()) { - throw exception(ORDER_CANCEL_PAID_FAIL, "已支付"); - } // 2. 更新订单赠送的优惠券编号列表 tradeOrderMapper.updateById(new TradeOrderDO().setId(orderId).setGiveCouponIds(giveCouponIds)); From fdaf5e50ca39367377c9225924ac47606d951598 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Mon, 2 Sep 2024 16:28:13 +0800 Subject: [PATCH 34/42] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E5=95=86=E5=9F=8E:=20=E6=BB=A1=E5=87=8F?= =?UTF-8?q?=E9=80=81=E6=B4=BB=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/reward/RewardActivityController.java | 11 +++++------ .../admin/reward/vo/RewardActivityBaseVO.java | 8 ++++---- .../convert/reward/RewardActivityConvert.java | 13 ------------- .../dal/dataobject/reward/RewardActivityDO.java | 4 ++-- .../service/reward/RewardActivityServiceImpl.java | 5 ++--- 5 files changed, 13 insertions(+), 28 deletions(-) diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/RewardActivityController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/RewardActivityController.java index d41912337..0e50ffc14 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/RewardActivityController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/RewardActivityController.java @@ -2,23 +2,22 @@ package cn.iocoder.yudao.module.promotion.controller.admin.reward; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityCreateReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityRespVO; import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityUpdateReqVO; -import cn.iocoder.yudao.module.promotion.convert.reward.RewardActivityConvert; import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO; import cn.iocoder.yudao.module.promotion.service.reward.RewardActivityService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import jakarta.annotation.Resource; -import jakarta.validation.Valid; - import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @Tag(name = "管理后台 - 满减送活动") @@ -69,7 +68,7 @@ public class RewardActivityController { @PreAuthorize("@ss.hasPermission('promotion:reward-activity:query')") public CommonResult getRewardActivity(@RequestParam("id") Long id) { RewardActivityDO rewardActivity = rewardActivityService.getRewardActivity(id); - return success(RewardActivityConvert.INSTANCE.convert(rewardActivity)); + return success(BeanUtils.toBean(rewardActivity, RewardActivityRespVO.class)); } @GetMapping("/page") @@ -77,7 +76,7 @@ public class RewardActivityController { @PreAuthorize("@ss.hasPermission('promotion:reward-activity:query')") public CommonResult> getRewardActivityPage(@Valid RewardActivityPageReqVO pageVO) { PageResult pageResult = rewardActivityService.getRewardActivityPage(pageVO); - return success(RewardActivityConvert.INSTANCE.convertPage(pageResult)); + return success(BeanUtils.toBean(pageResult, RewardActivityRespVO.class)); } } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java index 7f68ee123..31c40d9de 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java @@ -79,13 +79,13 @@ public class RewardActivityBaseVO { @Schema(description = "赠送的积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") private Integer point; - @Schema(description = "赠送的优惠劵编号的数组", example = "1,2,3") - private Map giveCouponsMap; + @Schema(description = "赠送的优惠劵编号的数组") + private Map giveCoupons; - @AssertTrue(message = "赠送的积分不能小于 1") + @AssertTrue(message = "赠送的积分不能小于 0") @JsonIgnore public boolean isPointValid() { - return point == null || point >= 1; + return point == null || point >= 0; } } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/reward/RewardActivityConvert.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/reward/RewardActivityConvert.java index 5343656ed..c954100c5 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/reward/RewardActivityConvert.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/reward/RewardActivityConvert.java @@ -1,10 +1,5 @@ package cn.iocoder.yudao.module.promotion.convert.reward; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityCreateReqVO; -import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityRespVO; -import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityUpdateReqVO; -import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO; import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; @@ -18,12 +13,4 @@ public interface RewardActivityConvert { RewardActivityConvert INSTANCE = Mappers.getMapper(RewardActivityConvert.class); - RewardActivityDO convert(RewardActivityCreateReqVO bean); - - RewardActivityDO convert(RewardActivityUpdateReqVO bean); - - RewardActivityRespVO convert(RewardActivityDO bean); - - PageResult convertPage(PageResult page); - } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java index f5d60c4e6..03e052a69 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java @@ -107,9 +107,9 @@ public class RewardActivityDO extends BaseDO { /** * 赠送的优惠劵 * - * key: 优惠劵编号,value:对应的优惠券数量 + * key: 优惠劵模版编号,value:对应的数量 */ - private Map giveCouponsMap; + private Map giveCoupons; } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java index d35142c19..eefbc6dee 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java @@ -10,7 +10,6 @@ import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivi import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityCreateReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityUpdateReqVO; -import cn.iocoder.yudao.module.promotion.convert.reward.RewardActivityConvert; import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO; import cn.iocoder.yudao.module.promotion.dal.mysql.reward.RewardActivityMapper; import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; @@ -54,7 +53,7 @@ public class RewardActivityServiceImpl implements RewardActivityService { validateRewardActivitySpuConflicts(null, createReqVO); // 2. 插入 - RewardActivityDO rewardActivity = RewardActivityConvert.INSTANCE.convert(createReqVO) + RewardActivityDO rewardActivity = BeanUtils.toBean(createReqVO, RewardActivityDO.class) .setStatus(PromotionUtils.calculateActivityStatus(createReqVO.getEndTime())); rewardActivityMapper.insert(rewardActivity); // 返回 @@ -74,7 +73,7 @@ public class RewardActivityServiceImpl implements RewardActivityService { validateRewardActivitySpuConflicts(updateReqVO.getId(), updateReqVO); // 2. 更新 - RewardActivityDO updateObj = RewardActivityConvert.INSTANCE.convert(updateReqVO) + RewardActivityDO updateObj = BeanUtils.toBean(updateReqVO, RewardActivityDO.class) .setStatus(PromotionUtils.calculateActivityStatus(updateReqVO.getEndTime())); rewardActivityMapper.updateById(updateObj); } From 79cb96702ad4144aac1a365a12df03918a7093b5 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Mon, 2 Sep 2024 17:20:41 +0800 Subject: [PATCH 35/42] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E5=95=86=E5=9F=8E:=20=E6=BB=A1=E5=87=8F?= =?UTF-8?q?=E9=80=81=E6=B4=BB=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/reward/dto/RewardActivityMatchRespDTO.java | 4 +++- .../admin/reward/vo/RewardActivityBaseVO.java | 2 +- .../dal/dataobject/reward/RewardActivityDO.java | 7 +++++-- .../trade/dal/dataobject/order/TradeOrderDO.java | 3 +-- .../service/order/TradeOrderUpdateServiceImpl.java | 2 +- .../order/handler/TradeCouponOrderHandler.java | 4 ++-- .../service/price/bo/TradePriceCalculateRespBO.java | 8 +++++--- .../price/calculator/TradePriceCalculatorHelper.java | 2 +- .../TradeRewardActivityPriceCalculator.java | 12 ++++++------ .../TradeRewardActivityPriceCalculatorTest.java | 2 +- 10 files changed, 26 insertions(+), 20 deletions(-) diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java index d8d5ef135..958668461 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java @@ -95,8 +95,10 @@ public class RewardActivityMatchRespDTO { * * key: 优惠劵模版编号 * value:对应的优惠券数量 + * + * 目的:用于订单支付后赠送优惠券 */ - private Map giveCoupons; + private Map giveCouponTemplateCounts; } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java index 31c40d9de..590e9a7f2 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java @@ -80,7 +80,7 @@ public class RewardActivityBaseVO { private Integer point; @Schema(description = "赠送的优惠劵编号的数组") - private Map giveCoupons; + private Map giveCouponTemplateCounts; @AssertTrue(message = "赠送的积分不能小于 0") @JsonIgnore diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java index 03e052a69..a2f1e7e88 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java @@ -107,9 +107,12 @@ public class RewardActivityDO extends BaseDO { /** * 赠送的优惠劵 * - * key: 优惠劵模版编号,value:对应的数量 + * key: 优惠劵模版编号 + * value:对应的优惠券数量 + * + * 目的:用于订单支付后赠送优惠券 */ - private Map giveCoupons; + private Map giveCouponTemplateCounts; } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java index 4cfee5e17..399b692ed 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java @@ -296,7 +296,6 @@ public class TradeOrderDO extends BaseDO { */ private Integer vipPrice; - // TODO @puhui999:我们要不要把相关的字段,定义的更明确一点?例如说,giveCouponTemplateCounts 赠送的优惠劵模版数量,或者 giveCouponCounts 赠送的优惠劵数量。感受上,Coupons 和 Map 有点点重叠哈。 /** * 赠送的优惠劵 * @@ -306,7 +305,7 @@ public class TradeOrderDO extends BaseDO { * 目的:用于订单支付后赠送优惠券 */ @TableField(typeHandler = JacksonTypeHandler.class) - private Map giveCouponsMap; + private Map giveCouponTemplateCounts; /** * 赠送的优惠劵编号 * diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java index 379be205f..ce0c953e1 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java @@ -202,7 +202,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum)); order.setUserIp(getClientIP()).setTerminal(getTerminal()); // 使用 + 赠送优惠券 - order.setGiveCouponsMap(calculateRespBO.getGiveCoupons()); + order.setGiveCouponTemplateCounts(calculateRespBO.getGiveCouponTemplateCounts()); // 支付 + 退款信息 order.setAdjustPrice(0).setPayStatus(false); order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()).setRefundPrice(0); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java index 3a98a6c9e..f5d7da4d4 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java @@ -42,11 +42,11 @@ public class TradeCouponOrderHandler implements TradeOrderHandler { @Override public void afterPayOrder(TradeOrderDO order, List orderItems) { - if (CollUtil.isEmpty(order.getGiveCouponsMap())) { + if (CollUtil.isEmpty(order.getGiveCouponTemplateCounts())) { return; } // 赠送优惠券 - List couponIds = couponApi.takeCouponsByAdmin(order.getGiveCouponsMap(), order.getUserId()); + List couponIds = couponApi.takeCouponsByAdmin(order.getGiveCouponTemplateCounts(), order.getUserId()); if (CollUtil.isEmpty(couponIds)) { return; } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java index 68fa58b37..4f65f33d1 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java @@ -76,10 +76,12 @@ public class TradePriceCalculateRespBO { /** * 赠送的优惠劵 * - * key: 优惠劵编号,value:对应的优惠券数量 - * 目的:用于后续取消或者售后订单时,需要扣减赠送 + * key: 优惠劵模版编号 + * value:对应的优惠券数量 + * + * 目的:用于订单支付后赠送优惠券 */ - private Map giveCoupons; + private Map giveCouponTemplateCounts; /** * 订单价格 diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java index 195ef8718..323b50e93 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java @@ -32,7 +32,7 @@ public class TradePriceCalculatorHelper { List spuList, List skuList) { // 创建 PriceCalculateRespDTO 对象 TradePriceCalculateRespBO result = new TradePriceCalculateRespBO(); - result.setType(getOrderType(param)).setPromotions(new ArrayList<>()).setGiveCoupons(new LinkedHashMap<>()); + result.setType(getOrderType(param)).setPromotions(new ArrayList<>()).setGiveCouponTemplateCounts(new LinkedHashMap<>()); // 创建它的 OrderItem 属性 result.setItems(new ArrayList<>(param.getItems().size())); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java index f62b65eb9..ddb24e9bd 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java @@ -107,13 +107,13 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator result.setFreeDelivery(true); } // 4.3 记录赠送的优惠券 - if (CollUtil.isNotEmpty(rule.getGiveCoupons())) { - for (Map.Entry entry : rule.getGiveCoupons().entrySet()) { - Map giveCoupons = result.getGiveCoupons(); - if (giveCoupons.get(entry.getKey()) == null) { // 情况一:还没有赠送的优惠券 - result.setGiveCoupons(rule.getGiveCoupons()); + if (CollUtil.isNotEmpty(rule.getGiveCouponTemplateCounts())) { + for (Map.Entry entry : rule.getGiveCouponTemplateCounts().entrySet()) { + Map giveCouponTemplateCounts = result.getGiveCouponTemplateCounts(); + if (giveCouponTemplateCounts.get(entry.getKey()) == null) { // 情况一:还没有赠送的优惠券 + result.setGiveCouponTemplateCounts(rule.getGiveCouponTemplateCounts()); } else { // 情况二:别的满减活动送过同类优惠券,则直接增加数量 - giveCoupons.put(entry.getKey(), giveCoupons.get(entry.getKey()) + entry.getValue()); + giveCouponTemplateCounts.put(entry.getKey(), giveCouponTemplateCounts.get(entry.getKey()) + entry.getValue()); } } } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java index f1f31e3c8..ba93fc10e 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java @@ -49,7 +49,7 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest TradePriceCalculateRespBO result = new TradePriceCalculateRespBO() .setType(TradeOrderTypeEnum.NORMAL.getType()) .setPrice(new TradePriceCalculateRespBO.Price()) - .setPromotions(new ArrayList<>()).setGiveCoupons(new LinkedHashMap<>()) + .setPromotions(new ArrayList<>()).setGiveCouponTemplateCounts(new LinkedHashMap<>()) .setItems(asList( new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true) .setPrice(100).setSpuId(1L), From e5453028f10c1e7879739ebeca1dc5966edc1fd7 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 2 Sep 2024 21:53:41 +0800 Subject: [PATCH 36/42] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91=E5=95=86=E5=9F=8E=EF=BC=9A=E6=BB=A1=E5=87=8F?= =?UTF-8?q?=E9=80=81=E8=AE=A2=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../convert/reward/RewardActivityConvert.java | 16 ---------------- .../TradeRewardActivityPriceCalculator.java | 4 ++++ 2 files changed, 4 insertions(+), 16 deletions(-) delete mode 100755 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/reward/RewardActivityConvert.java diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/reward/RewardActivityConvert.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/reward/RewardActivityConvert.java deleted file mode 100755 index c954100c5..000000000 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/reward/RewardActivityConvert.java +++ /dev/null @@ -1,16 +0,0 @@ -package cn.iocoder.yudao.module.promotion.convert.reward; - -import org.mapstruct.Mapper; -import org.mapstruct.factory.Mappers; - -/** - * 满减送活动 Convert - * - * @author 芋道源码 - */ -@Mapper -public interface RewardActivityConvert { - - RewardActivityConvert INSTANCE = Mappers.getMapper(RewardActivityConvert.class); - -} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java index ddb24e9bd..50d424c29 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java @@ -110,6 +110,10 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator if (CollUtil.isNotEmpty(rule.getGiveCouponTemplateCounts())) { for (Map.Entry entry : rule.getGiveCouponTemplateCounts().entrySet()) { Map giveCouponTemplateCounts = result.getGiveCouponTemplateCounts(); + // TODO @puhui999:是不是有一种可能性,这个 key 没有,别的 key 有哈。 + // TODO 这里还有一种简化的写法。就是下面,大概两行就可以啦 +// result.getGiveCouponTemplateCounts().put(entry.getKey(), +// result.getGiveCouponTemplateCounts().getOrDefault(entry.getKey(), 0) + entry.getValue()); if (giveCouponTemplateCounts.get(entry.getKey()) == null) { // 情况一:还没有赠送的优惠券 result.setGiveCouponTemplateCounts(rule.getGiveCouponTemplateCounts()); } else { // 情况二:别的满减活动送过同类优惠券,则直接增加数量 From 9fbc953dd1ff6f11470bcda12f48e4845982ce59 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 3 Sep 2024 08:54:56 +0800 Subject: [PATCH 37/42] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E3=80=91INFRA=EF=BC=9A=E4=BB=A3=E7=A0=81=E7=94=9F?= =?UTF-8?q?=E6=88=90=E5=9C=A8=20ERP=20=E6=A8=A1=E5=BC=8F=E6=97=B6=EF=BC=8C?= =?UTF-8?q?updateTime=20=E6=97=A0=E6=B3=95=E8=A2=AB=E6=AD=A3=E7=A1=AE?= =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/codegen/java/service/serviceImpl.vm | 1 + 1 file changed, 1 insertion(+) diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/service/serviceImpl.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/service/serviceImpl.vm index a8184e4d7..80bc71b02 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/service/serviceImpl.vm +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/service/serviceImpl.vm @@ -286,6 +286,7 @@ public class ${table.className}ServiceImpl implements ${table.className}Service // 校验存在 validate${subSimpleClassName}Exists(${subClassNameVar}.getId()); // 更新 + ${subClassNameVar}.setUpdater(null).setUpdateTime(null); // 解决更新情况下:updateTime 不更新 ${subClassNameVars.get($index)}Mapper.updateById(${subClassNameVar}); } From d3f28b92a76af82d78977bd14650a13e9fc10f20 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 3 Sep 2024 10:34:44 +0800 Subject: [PATCH 38/42] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91SYSTEM=EF=BC=9A=E8=AE=A4=E8=AF=81=E4=BB=A4?= =?UTF-8?q?=E7=89=8C=E7=9A=84=E6=93=8D=E4=BD=9C=EF=BC=8C=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=20@Transactional=20=E6=B3=A8=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 6 +- .../oauth2/OAuth2TokenServiceImpl.java | 6 +- yudao-server/pom.xml | 60 +++++++++---------- 3 files changed, 37 insertions(+), 35 deletions(-) diff --git a/pom.xml b/pom.xml index 86dfebcc3..4634d345e 100644 --- a/pom.xml +++ b/pom.xml @@ -15,12 +15,12 @@ yudao-module-system yudao-module-infra - + yudao-module-member - - + yudao-module-pay + yudao-module-mall diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImpl.java index cb3bf409f..8918e7ede 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImpl.java @@ -56,7 +56,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { private AdminUserService adminUserService; @Override - @Transactional + @Transactional(rollbackFor = Exception.class) public OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, String clientId, List scopes) { OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId); // 创建刷新令牌 @@ -66,6 +66,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { } @Override + @Transactional(rollbackFor = Exception.class) public OAuth2AccessTokenDO refreshAccessToken(String refreshToken, String clientId) { // 查询访问令牌 OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectByRefreshToken(refreshToken); @@ -82,7 +83,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { // 移除相关的访问令牌 List accessTokenDOs = oauth2AccessTokenMapper.selectListByRefreshToken(refreshToken); if (CollUtil.isNotEmpty(accessTokenDOs)) { - oauth2AccessTokenMapper.deleteBatchIds(convertSet(accessTokenDOs, OAuth2AccessTokenDO::getId)); + oauth2AccessTokenMapper.deleteByIds(convertSet(accessTokenDOs, OAuth2AccessTokenDO::getId)); oauth2AccessTokenRedisDAO.deleteList(convertSet(accessTokenDOs, OAuth2AccessTokenDO::getAccessToken)); } @@ -126,6 +127,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { } @Override + @Transactional(rollbackFor = Exception.class) public OAuth2AccessTokenDO removeAccessToken(String accessToken) { // 删除访问令牌 OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(accessToken); diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml index 3b16fa192..d0c429aab 100644 --- a/yudao-server/pom.xml +++ b/yudao-server/pom.xml @@ -33,11 +33,11 @@ - - - - - + + cn.iocoder.boot + yudao-module-member-biz + ${revision} + @@ -52,11 +52,11 @@ - - - - - + + cn.iocoder.boot + yudao-module-pay-biz + ${revision} + @@ -66,26 +66,26 @@ - - - - - - - - - - - - - - - - - - - - + + cn.iocoder.boot + yudao-module-promotion-biz + ${revision} + + + cn.iocoder.boot + yudao-module-product-biz + ${revision} + + + cn.iocoder.boot + yudao-module-trade-biz + ${revision} + + + cn.iocoder.boot + yudao-module-statistics-biz + ${revision} + From 5692571a9c02c9801a5e2b1208463349e6c50365 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 3 Sep 2024 10:47:20 +0800 Subject: [PATCH 39/42] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E5=95=86=E5=9F=8E=EF=BC=9A=E4=BB=B7=E6=A0=BC?= =?UTF-8?q?=E8=AE=A1=E7=AE=97=E6=97=B6=EF=BC=8C=E5=A2=9E=E5=8A=A0=E2=80=9C?= =?UTF-8?q?=E8=AF=A5=E4=BC=98=E6=83=A0=E5=8A=B5=E6=97=A0=E6=B3=95=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=EF=BC=8C=E5=8E=9F=E5=9B=A0=EF=BC=9A=E4=BC=98=E6=83=A0?= =?UTF-8?q?=E9=87=91=E9=A2=9D=E8=B6=85=E8=BF=87=E8=AE=A2=E5=8D=95=E9=87=91?= =?UTF-8?q?=E9=A2=9D=E2=80=9D=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/module/trade/enums/ErrorCodeConstants.java | 1 + .../trade/service/brokerage/BrokerageUserServiceImpl.java | 5 ----- .../price/calculator/TradeCouponPriceCalculator.java | 6 ++++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java index a797fa5bd..5613cae8e 100644 --- a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java +++ b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java @@ -61,6 +61,7 @@ public interface ErrorCodeConstants { ErrorCode PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER = new ErrorCode(1_011_003_004, "参与秒杀、拼团、砍价的营销商品,无法使用优惠劵"); ErrorCode PRICE_CALCULATE_SECKILL_TOTAL_LIMIT_COUNT = new ErrorCode(1_011_003_005, "参与秒杀的商品,超过了秒杀总限购数量"); ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TYPE_ILLEGAL = new ErrorCode(1_011_003_006, "计算快递运费异常,配送方式不匹配"); + ErrorCode PRICE_CALCULATE_COUPON_PRICE_TOO_MUCH = new ErrorCode(1_011_003_007, "该优惠劵无法使用,原因:优惠金额超过订单金额"); // ========== 物流 Express 模块 1-011-004-000 ========== ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1_011_004_000, "快递公司不存在"); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImpl.java index cf56a5bce..751151fe8 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImpl.java @@ -268,11 +268,6 @@ public class BrokerageUserServiceImpl implements BrokerageUserService { return false; } - // 校验分佣模式:仅可后台手动设置推广员 - // if (BrokerageEnabledConditionEnum.ADMIN.getCondition().equals(tradeConfig.getBrokerageEnabledCondition())) { - // throw exception(BROKERAGE_BIND_CONDITION_ADMIN); - // } - // 校验分销关系绑定模式 if (BrokerageBindModeEnum.REGISTER.getMode().equals(tradeConfig.getBrokerageBindMode())) { // 判断是否为新用户:注册时间在 30 秒内的,都算新用户 diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculator.java index 3bdfe509f..1c7294be5 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculator.java @@ -25,6 +25,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_NO_MATCH_MIN_PRICE; import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_NO_MATCH_SPU; import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER; +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_COUPON_PRICE_TOO_MUCH; /** * 优惠劵的 {@link TradePriceCalculator} 实现类 @@ -65,8 +66,9 @@ public class TradeCouponPriceCalculator implements TradePriceCalculator { // 3.1 计算可以优惠的金额 Integer couponPrice = getCouponPrice(coupon, totalPayPrice); - Assert.isTrue(couponPrice < totalPayPrice, - "优惠劵({}) 的优惠金额({}),不能大于订单总金额({})", coupon.getId(), couponPrice, totalPayPrice); + if (couponPrice <= totalPayPrice) { + throw exception(PRICE_CALCULATE_COUPON_PRICE_TOO_MUCH); + } // 3.2 计算分摊的优惠金额 List divideCouponPrices = TradePriceCalculatorHelper.dividePrice(orderItems, couponPrice); From acf0e401d3c188901f0d811a2af043ecef496b1c Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 3 Sep 2024 16:11:08 +0800 Subject: [PATCH 40/42] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91SYSTEM=EF=BC=9Auser=5Frole=5Fids=20=E5=BF=BD?= =?UTF-8?q?=E7=95=A5=E5=A4=9A=E7=A7=9F=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-server/src/main/resources/application.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index 8d63d9593..efe1eb34f 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -301,6 +301,7 @@ yudao: - tmp_report_data_1 - tmp_report_data_income ignore-caches: + - user_role_ids - permission_menu_ids - oauth_client - notify_template From ae9ff74e2353d0709b035f79798bf9f8f4fa01d1 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 4 Sep 2024 09:07:16 +0800 Subject: [PATCH 41/42] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E5=85=A8=E5=B1=80=EF=BC=9AMySQL=20JDBC=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20rewriteBatchedStatements=20=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E6=8F=92=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-server/src/main/resources/application-dev.yaml | 2 +- yudao-server/src/main/resources/application-local.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/yudao-server/src/main/resources/application-dev.yaml b/yudao-server/src/main/resources/application-dev.yaml index 46399b802..7e2fcc1c1 100644 --- a/yudao-server/src/main/resources/application-dev.yaml +++ b/yudao-server/src/main/resources/application-dev.yaml @@ -40,7 +40,7 @@ spring: primary: master datasource: master: - url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 + url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例 username: root password: 123456 slave: # 模拟从库,可根据自己需要修改 # 模拟从库,可根据自己需要修改 diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index 0c27aeac8..35715d98c 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -45,8 +45,8 @@ spring: primary: master datasource: master: - url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 - # url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=true&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai # MySQL Connector/J 5.X 连接的示例 + url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例 + # url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=true&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true # MySQL Connector/J 5.X 连接的示例 # url: jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro # PostgreSQL 连接的示例 # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=ruoyi-vue-pro;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true;useUnicode=true;characterEncoding=utf-8 # SQLServer 连接的示例 From 15f46db7acaf9e3ea85a559a2ad9537eb24f58d0 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 4 Sep 2024 09:08:12 +0800 Subject: [PATCH 42/42] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E5=85=A8=E5=B1=80=EF=BC=9AMySQL=20JDBC=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20rewriteBatchedStatements=20=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E6=8F=92=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-server/src/main/resources/application-dev.yaml | 2 +- yudao-server/src/main/resources/application-local.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/yudao-server/src/main/resources/application-dev.yaml b/yudao-server/src/main/resources/application-dev.yaml index 7e2fcc1c1..5a4fa9286 100644 --- a/yudao-server/src/main/resources/application-dev.yaml +++ b/yudao-server/src/main/resources/application-dev.yaml @@ -45,7 +45,7 @@ spring: password: 123456 slave: # 模拟从库,可根据自己需要修改 # 模拟从库,可根据自己需要修改 lazy: true # 开启懒加载,保证启动速度 - url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 + url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例 username: root password: 123456 diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index 35715d98c..40c0919b7 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -63,7 +63,7 @@ spring: # password: Yudao@2024 # OpenGauss 连接的示例 slave: # 模拟从库,可根据自己需要修改 lazy: true # 开启懒加载,保证启动速度 - url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true + url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true username: root password: 123456