Compare commits

..

1 Commits

Author SHA1 Message Date
芋道源码
224b4a0833 Add delivery method validation in price calculation
---

For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/YunaiV/ruoyi-vue-pro?shareId=XXXX-XXXX-XXXX-XXXX).
2024-08-12 14:31:31 +08:00
9 changed files with 70 additions and 84 deletions

View File

@@ -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;
// ========== 营销相关字段 =========
/**

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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));
}
}

View File

@@ -154,14 +154,4 @@ public class AuthController {
return success(authService.socialLogin(reqVO));
}
// ========== 邮箱注册相关 ==========
@PostMapping("/register")
@PermitAll
@Operation(summary = "使用邮箱和密码注册")
public CommonResult<Boolean> register(@RequestBody @Valid AuthRegisterReqVO reqVO) {
userService.registerUser(reqVO);
return success(true);
}
}

View File

@@ -1,24 +0,0 @@
package cn.iocoder.yudao.module.system.controller.admin.auth.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
@Schema(description = "管理后台 - 用户注册 Request VO")
@Data
public class AuthRegisterReqVO {
@Schema(description = "用户邮箱", requiredMode = Schema.RequiredMode.REQUIRED, example = "test@example.com")
@NotBlank(message = "用户邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
@NotBlank(message = "密码不能为空")
@Size(min = 4, max = 16, message = "密码长度为 4-16 位")
private String password;
}

View File

@@ -77,16 +77,4 @@ public class UserSaveReqVO {
|| (ObjectUtil.isAllNotEmpty(password)); // 新增时,必须都传递 password
}
// ========== 新增字段 ==========
@Schema(description = "用户邮箱", requiredMode = Schema.RequiredMode.REQUIRED, example = "test@example.com")
@NotBlank(message = "用户邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String registrationEmail;
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
@NotBlank(message = "密码不能为空")
@Size(min = 4, max = 16, message = "密码长度为 4-16 位")
private String registrationPassword;
}

View File

@@ -201,12 +201,4 @@ public interface AdminUserService {
*/
boolean isPasswordMatch(String rawPassword, String encodedPassword);
/**
* 注册用户
*
* @param registerReqVO 注册信息
* @return 用户编号
*/
Long registerUser(@Valid AuthRegisterReqVO registerReqVO);
}

View File

@@ -13,7 +13,6 @@ import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils;
import cn.iocoder.yudao.module.infra.api.config.ConfigApi;
import cn.iocoder.yudao.module.infra.api.file.FileApi;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthRegisterReqVO;
import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO;
import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO;
import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserImportExcelVO;
@@ -22,14 +21,12 @@ import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserPageReqV
import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserSaveReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.UserPostDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.dal.mysql.dept.UserPostMapper;
import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper;
import cn.iocoder.yudao.module.system.service.dept.DeptService;
import cn.iocoder.yudao.module.system.service.dept.PostService;
import cn.iocoder.yudao.module.system.service.permission.PermissionService;
import cn.iocoder.yudao.module.system.service.permission.RoleService;
import cn.iocoder.yudao.module.system.service.tenant.TenantService;
import com.google.common.annotations.VisibleForTesting;
import com.mzt.logapi.context.LogRecordContext;
@@ -87,9 +84,6 @@ public class AdminUserServiceImpl implements AdminUserService {
@Resource
private ConfigApi configApi;
@Resource
private RoleService roleService;
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_CREATE_SUB_TYPE, bizNo = "{{#user.id}}",
@@ -498,28 +492,6 @@ public class AdminUserServiceImpl implements AdminUserService {
return passwordEncoder.matches(rawPassword, encodedPassword);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long registerUser(AuthRegisterReqVO registerReqVO) {
// 1. 校验邮箱是否已被使用
validateEmailUnique(null, registerReqVO.getEmail());
// 2. 创建用户
AdminUserDO user = new AdminUserDO();
user.setEmail(registerReqVO.getEmail());
user.setPassword(encodePassword(registerReqVO.getPassword()));
user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认启用
userMapper.insert(user);
// 3. 赋予 'normal' 角色
RoleDO normalRole = roleService.getRoleByCode("normal");
if (normalRole != null) {
permissionService.assignRoleToUser(user.getId(), normalRole.getId());
}
return user.getId();
}
/**
* 对密码进行加密
*