Compare commits
1 Commits
add-qiniu-
...
add-delive
Author | SHA1 | Date | |
---|---|---|---|
![]() |
224b4a0833 |
@@ -3,7 +3,8 @@ package cn.iocoder.yudao.module.product.api.spu.dto;
|
||||
import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
|
||||
import lombok.Data;
|
||||
|
||||
// TODO @LeeYan9: ProductSpuRespDTO
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 商品 SPU 信息 Response DTO
|
||||
*
|
||||
@@ -76,6 +77,11 @@ public class ProductSpuRespDTO {
|
||||
*/
|
||||
private Long deliveryTemplateId;
|
||||
|
||||
/**
|
||||
* 支持的配送方式
|
||||
*/
|
||||
private List<Integer> deliveryTypes;
|
||||
|
||||
// ========== 营销相关字段 =========
|
||||
|
||||
/**
|
||||
|
@@ -4,6 +4,7 @@ import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
|
||||
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
|
||||
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.enums.delivery.DeliveryTypeEnum;
|
||||
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.calculator.TradePriceCalculator;
|
||||
@@ -15,6 +16,8 @@ import org.springframework.validation.annotation.Validated;
|
||||
import jakarta.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
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.convertMap;
|
||||
@@ -22,6 +25,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
|
||||
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
|
||||
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_STOCK_NOT_ENOUGH;
|
||||
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_PAY_PRICE_ILLEGAL;
|
||||
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_DELIVERY_TYPE_NOT_SUPPORTED;
|
||||
|
||||
/**
|
||||
* 价格计算 Service 实现类
|
||||
@@ -48,6 +52,9 @@ public class TradePriceServiceImpl implements TradePriceService {
|
||||
// 1.2 获得商品 SPU 数组
|
||||
List<ProductSpuRespDTO> spuList = checkSpuList(skuList);
|
||||
|
||||
// 1.3 校验配送方式是否匹配
|
||||
checkDeliveryType(calculateReqBO, spuList);
|
||||
|
||||
// 2.1 计算价格
|
||||
TradePriceCalculateRespBO calculateRespBO = TradePriceCalculatorHelper
|
||||
.buildCalculateResp(calculateReqBO, spuList, skuList);
|
||||
@@ -85,4 +92,17 @@ public class TradePriceServiceImpl implements TradePriceService {
|
||||
return productSpuApi.validateSpuList(convertSet(skuList, ProductSkuRespDTO::getSpuId));
|
||||
}
|
||||
|
||||
private void checkDeliveryType(TradePriceCalculateReqBO reqBO, List<ProductSpuRespDTO> spuList) {
|
||||
if (reqBO.getDeliveryType() == null) {
|
||||
return;
|
||||
}
|
||||
Set<Integer> supportedDeliveryTypes = spuList.stream()
|
||||
.flatMap(spu -> spu.getDeliveryTypes().stream())
|
||||
.collect(Collectors.toSet());
|
||||
if (!supportedDeliveryTypes.contains(reqBO.getDeliveryType())) {
|
||||
log.error("[checkDeliveryType][配送方式不匹配,请求 deliveryType({}),支持的 deliveryTypes({})]",
|
||||
reqBO.getDeliveryType(), supportedDeliveryTypes);
|
||||
throw exception(PRICE_CALCULATE_DELIVERY_TYPE_NOT_SUPPORTED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -223,4 +223,17 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkDeliveryType(TradePriceCalculateReqBO param, List<ProductSpuRespDTO> spuList) {
|
||||
if (param.getDeliveryType() == null) {
|
||||
return;
|
||||
}
|
||||
Set<Integer> supportedDeliveryTypes = spuList.stream()
|
||||
.flatMap(spu -> spu.getDeliveryTypes().stream())
|
||||
.collect(Collectors.toSet());
|
||||
if (!supportedDeliveryTypes.contains(param.getDeliveryType())) {
|
||||
log.error("[checkDeliveryType][配送方式不匹配,请求 deliveryType({}),支持的 deliveryTypes({})]",
|
||||
param.getDeliveryType(), supportedDeliveryTypes);
|
||||
throw exception(PRICE_CALCULATE_DELIVERY_TYPE_NOT_SUPPORTED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -30,7 +30,6 @@ import static org.mockito.Mockito.when;
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Disabled // TODO 芋艿:后续 fix 补充的单测
|
||||
public class TradePriceServiceImplTest extends BaseMockitoUnitTest {
|
||||
|
||||
@InjectMocks
|
||||
@@ -134,4 +133,34 @@ public class TradePriceServiceImplTest extends BaseMockitoUnitTest {
|
||||
assertEquals(skuList.get(2).getProperties(), calculateRespBO.getItems().get(2).getProperties());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCalculatePrice_DeliveryTypeMismatch() {
|
||||
// 准备参数
|
||||
TradePriceCalculateReqBO calculateReqBO = new TradePriceCalculateReqBO()
|
||||
.setUserId(10L)
|
||||
.setCouponId(20L).setAddressId(30L)
|
||||
.setDeliveryType(DeliveryTypeEnum.EXPRESS.getType()) // 设置配送方式
|
||||
.setItems(Arrays.asList(
|
||||
new TradePriceCalculateReqBO.Item().setSkuId(100L).setCount(1).setSelected(true),
|
||||
new TradePriceCalculateReqBO.Item().setSkuId(200L).setCount(3).setSelected(true)
|
||||
));
|
||||
// mock 方法
|
||||
List<ProductSkuRespDTO> skuList = Arrays.asList(
|
||||
new ProductSkuRespDTO().setId(100L).setStock(500).setPrice(1000).setPicUrl("https://t.cn/1.png").setSpuId(1001L)
|
||||
.setProperties(singletonList(new ProductPropertyValueDetailRespDTO().setPropertyId(1L).setPropertyName("颜色")
|
||||
.setValueId(2L).setValueName("红色"))),
|
||||
new ProductSkuRespDTO().setId(200L).setStock(400).setPrice(2000).setPicUrl("https://t.cn/2.png").setSpuId(1001L)
|
||||
.setProperties(singletonList(new ProductPropertyValueDetailRespDTO().setPropertyId(1L).setPropertyName("颜色")
|
||||
.setValueId(3L).setValueName("黄色")))
|
||||
);
|
||||
when(productSkuApi.getSkuList(Mockito.eq(asSet(100L, 200L)))).thenReturn(skuList);
|
||||
when(productSpuApi.getSpuList(Mockito.eq(asSet(1001L))))
|
||||
.thenReturn(singletonList(new ProductSpuRespDTO().setId(1001L).setName("小菜").setCategoryId(666L)
|
||||
.setStatus(ProductSpuStatusEnum.ENABLE.getStatus())
|
||||
.setDeliveryTypes(singletonList(DeliveryTypeEnum.PICK_UP.getType())))); // 设置不支持的配送方式
|
||||
|
||||
// 调用并断言抛出异常
|
||||
assertThrows(ServiceException.class, () -> tradePriceService.calculatePrice(calculateReqBO));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -52,13 +52,4 @@ public class SmsCallbackController {
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PostMapping("/qiniu")
|
||||
@PermitAll
|
||||
@Operation(summary = "七牛云短信的回调", description = "参见 https://www.qiniu.com/products/sms 文档")
|
||||
public CommonResult<Boolean> receiveQiniuSmsStatus(HttpServletRequest request) throws Throwable {
|
||||
String text = ServletUtils.getBody(request);
|
||||
smsSendService.receiveSmsStatus(SmsChannelEnum.QINIU.getCode(), text);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,134 +0,0 @@
|
||||
package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.iocoder.yudao.framework.common.core.KeyValue;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
|
||||
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;
|
||||
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.qiniu.sms.SmsManager;
|
||||
import com.qiniu.util.Auth;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* 七牛云短信客户端的实现类
|
||||
*/
|
||||
@Slf4j
|
||||
public class QiniuSmsClient extends AbstractSmsClient {
|
||||
|
||||
/**
|
||||
* 七牛云短信管理器
|
||||
*/
|
||||
private volatile SmsManager smsManager;
|
||||
|
||||
public QiniuSmsClient(SmsChannelProperties properties) {
|
||||
super(properties);
|
||||
Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空");
|
||||
Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInit() {
|
||||
Auth auth = Auth.create(properties.getApiKey(), properties.getApiSecret());
|
||||
smsManager = new SmsManager(auth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId,
|
||||
List<KeyValue<String, Object>> templateParams) throws Throwable {
|
||||
// 构建请求
|
||||
String[] mobiles = {mobile};
|
||||
String templateId = apiTemplateId;
|
||||
String templateParam = JsonUtils.toJsonString(MapUtils.convertMap(templateParams));
|
||||
// 执行请求
|
||||
com.qiniu.sms.model.SmsResponse response = smsManager.sendMessage(templateId, mobiles, templateParam);
|
||||
return new SmsSendRespDTO().setSuccess(response.isSuccessful()).setSerialNo(response.getJobId())
|
||||
.setApiRequestId(response.getRequestId()).setApiCode(response.getCode()).setApiMsg(response.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
|
||||
List<SmsReceiveStatus> statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class);
|
||||
return convertList(statuses, status -> new SmsReceiveRespDTO().setSuccess(status.getSuccess())
|
||||
.setErrorCode(status.getErrCode()).setErrorMsg(status.getErrMsg())
|
||||
.setMobile(status.getPhoneNumber()).setReceiveTime(status.getReportTime())
|
||||
.setSerialNo(status.getJobId()).setLogId(Long.valueOf(status.getOutId())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
|
||||
// 七牛云不支持查询短信模板,直接返回 null
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 短信接收状态
|
||||
*/
|
||||
@Data
|
||||
public static class SmsReceiveStatus {
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
@JsonProperty("phone_number")
|
||||
private String phoneNumber;
|
||||
/**
|
||||
* 发送时间
|
||||
*/
|
||||
@JsonProperty("send_time")
|
||||
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
|
||||
private LocalDateTime sendTime;
|
||||
/**
|
||||
* 状态报告时间
|
||||
*/
|
||||
@JsonProperty("report_time")
|
||||
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
|
||||
private LocalDateTime reportTime;
|
||||
/**
|
||||
* 是否接收成功
|
||||
*/
|
||||
private Boolean success;
|
||||
/**
|
||||
* 状态报告说明
|
||||
*/
|
||||
@JsonProperty("err_msg")
|
||||
private String errMsg;
|
||||
/**
|
||||
* 状态报告编码
|
||||
*/
|
||||
@JsonProperty("err_code")
|
||||
private String errCode;
|
||||
/**
|
||||
* 发送序列号
|
||||
*/
|
||||
@JsonProperty("job_id")
|
||||
private String jobId;
|
||||
/**
|
||||
* 用户序列号
|
||||
*/
|
||||
@JsonProperty("out_id")
|
||||
private String outId;
|
||||
/**
|
||||
* 短信长度
|
||||
*/
|
||||
@JsonProperty("sms_size")
|
||||
private Integer smsSize;
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -79,7 +79,6 @@ 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);
|
||||
|
@@ -18,7 +18,6 @@ public enum SmsChannelEnum {
|
||||
ALIYUN("ALIYUN", "阿里云"),
|
||||
TENCENT("TENCENT", "腾讯云"),
|
||||
HUAWEI("HUAWEI", "华为云"),
|
||||
QINIU("QINIU", "七牛云"),
|
||||
;
|
||||
|
||||
/**
|
||||
|
@@ -1,5 +0,0 @@
|
||||
sms:
|
||||
qiniu:
|
||||
apiKey: your_qiniu_api_key
|
||||
apiSecret: your_qiniu_api_secret
|
||||
signature: your_qiniu_signature
|
Reference in New Issue
Block a user