mall:价格计算的 API 简单实现

pull/2/head
YunaiV 2022-09-12 21:37:24 +08:00
parent 67d365475c
commit 15798cea10
39 changed files with 1017 additions and 375 deletions

View File

@ -19,15 +19,10 @@ public class PriceCalculateRespDTO {
*/ */
private Order order; private Order order;
/**
* SKU
*/
private List<Item> items;
/** /**
* *
* *
* {@link #items} * {@link Order#items}
*/ */
private List<Promotion> promotions; private List<Promotion> promotions;
@ -40,13 +35,13 @@ public class PriceCalculateRespDTO {
/** /**
* *
* *
* {@link Item#getTotalOriginalPrice()} * {@link OrderItem#getTotalOriginalPrice()}
*/ */
private Integer skuOriginalPrice; private Integer skuOriginalPrice;
/** /**
* *
* *
* {@link Item#getTotalPromotionPrice()} * {@link OrderItem#getTotalPromotionPrice()}
*/ */
private Integer skuPromotionPrice; private Integer skuPromotionPrice;
/** /**
@ -69,6 +64,10 @@ public class PriceCalculateRespDTO {
*/ */
// * - {@link #couponPrice} // TODO 芋艿:靠营销表记录 // * - {@link #couponPrice} // TODO 芋艿:靠营销表记录
private Integer payPrice; private Integer payPrice;
/**
* SKU
*/
private List<OrderItem> items;
// ========== 营销基本信息 ========== // ========== 营销基本信息 ==========
/** /**
@ -85,10 +84,15 @@ public class PriceCalculateRespDTO {
} }
/** /**
* SKU * SKU
*/ */
@Data @Data
public static class Item extends PriceCalculateReqDTO.Item { public static class OrderItem extends PriceCalculateReqDTO.Item {
/**
*
*/
private Integer count;
/** /**
* *
@ -140,6 +144,10 @@ public class PriceCalculateRespDTO {
* *
*/ */
private Long id; private Long id;
/**
*
*/
private String name;
/** /**
* *
* *
@ -152,10 +160,6 @@ public class PriceCalculateRespDTO {
* {@link PromotionLevelEnum} * {@link PromotionLevelEnum}
*/ */
private Integer level; private Integer level;
/**
* SKU
*/
private List<Item> items;
/** /**
* *
*/ */
@ -164,6 +168,13 @@ public class PriceCalculateRespDTO {
* *
*/ */
private Integer totalPromotionPrice; private Integer totalPromotionPrice;
/**
* SKU
*/
private List<PromotionItem> items;
// ========== 匹配情况 ==========
/** /**
* *
*/ */
@ -176,11 +187,13 @@ public class PriceCalculateRespDTO {
*/ */
private String meetTip; private String meetTip;
}
/** /**
* SKU * SKU
*/ */
@Data @Data
public static class Item { public static class PromotionItem {
/** /**
* SKU * SKU
@ -197,6 +210,4 @@ public class PriceCalculateRespDTO {
} }
}
} }

View File

@ -24,6 +24,11 @@
<artifactId>yudao-module-market-api</artifactId> <artifactId>yudao-module-market-api</artifactId>
<version>${revision}</version> <version>${revision}</version>
</dependency> </dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-product-api</artifactId>
<version>${revision}</version>
</dependency>
<!-- 业务组件 --> <!-- 业务组件 -->
<dependency> <dependency>

View File

@ -0,0 +1 @@
package cn.iocoder.yudao.module.market.api.discount;

View File

@ -1,24 +1,26 @@
package cn.iocoder.yudao.module.market.service.price; package cn.iocoder.yudao.module.market.api.price;
import cn.iocoder.yudao.module.market.api.price.PriceApi;
import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateReqDTO; import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateReqDTO;
import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO; import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO;
import cn.iocoder.yudao.module.market.service.price.PriceService;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/** /**
* API * API
* *
* TODO * @author
*
* @author TODO
*/ */
@Service @Service
public class PriceApiImpl implements PriceApi { public class PriceApiImpl implements PriceApi {
@Resource
private PriceService priceService;
@Override @Override
public PriceCalculateRespDTO calculatePrice(PriceCalculateReqDTO calculateReqDTO) { public PriceCalculateRespDTO calculatePrice(PriceCalculateReqDTO calculateReqDTO) {
// TODO fixme实现逻辑 return priceService.calculatePrice(calculateReqDTO);
return new PriceCalculateRespDTO();
} }
} }

View File

@ -0,0 +1,40 @@
package cn.iocoder.yudao.module.market.convert.price;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateReqDTO;
import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Mapper
public interface PriceConvert {
PriceConvert INSTANCE = Mappers.getMapper(PriceConvert.class);
default PriceCalculateRespDTO convert(PriceCalculateReqDTO calculateReqDTO, List<ProductSkuRespDTO> skuList) {
// 创建 PriceCalculateRespDTO 对象
PriceCalculateRespDTO priceCalculate = new PriceCalculateRespDTO();
priceCalculate.setOrder(new PriceCalculateRespDTO.Order().setSkuOriginalPrice(0).setSkuPromotionPrice(0)
.setOrderPromotionPrice(0).setDeliveryPrice(0).setPayPrice(0).setItems(new ArrayList<>())
.setCouponId(calculateReqDTO.getCouponId()));
priceCalculate.setPromotions(new ArrayList<>());
// 创建它的 OrderItem 属性
Map<Long, Integer> skuIdCountMap = CollectionUtils.convertMap(calculateReqDTO.getItems(),
PriceCalculateReqDTO.Item::getSkuId, PriceCalculateReqDTO.Item::getCount);
skuList.forEach(sku -> {
Integer count = skuIdCountMap.get(sku.getId());
PriceCalculateRespDTO.OrderItem orderItem = new PriceCalculateRespDTO.OrderItem().setCount(count)
.setOriginalPrice(sku.getPrice()).setTotalOriginalPrice(sku.getPrice() * count).setTotalPromotionPrice(0);
orderItem.setTotalPresentPrice(orderItem.getTotalPresentPrice()).setPresentPrice(orderItem.getOriginalPrice())
.setTotalPayPrice(orderItem.getTotalPayPrice());
priceCalculate.getOrder().getItems().add(orderItem);
});
return priceCalculate;
}
}

View File

@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.market.service.price;
import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateReqDTO;
import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO;
/**
* Service
*
* @author
*/
public interface PriceService {
/**
*
*
* @param calculateReqDTO
* @return
*/
PriceCalculateRespDTO calculatePrice(PriceCalculateReqDTO calculateReqDTO);
}

View File

@ -0,0 +1,64 @@
package cn.iocoder.yudao.module.market.service.price;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateReqDTO;
import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO;
import cn.iocoder.yudao.module.market.convert.price.PriceConvert;
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
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;
/**
* Service
*
* @author
*/
@Service
@Validated
public class PriceServiceImpl implements PriceService {
@Resource
private ProductSkuApi productSkuApi;
@Override
public PriceCalculateRespDTO calculatePrice(PriceCalculateReqDTO calculateReqDTO) {
// 获得商品 SKU 数组
List<ProductSkuRespDTO> skuList = checkProductSkus(calculateReqDTO);
// 初始化 PriceCalculateRespDTO 对象
PriceCalculateRespDTO priceCalculate = PriceConvert.INSTANCE.convert(calculateReqDTO, skuList);
// 计算【限时折扣】促销 TODO 待实现
// 计算【满减送】促销 TODO 待实现
// 计算【优惠劵】促销 TODO 待实现
return priceCalculate;
}
private List<ProductSkuRespDTO> checkProductSkus(PriceCalculateReqDTO calculateReqDTO) {
// 获得商品 SKU 数组
Map<Long, Integer> skuIdCountMap = CollectionUtils.convertMap(calculateReqDTO.getItems(),
PriceCalculateReqDTO.Item::getSkuId, PriceCalculateReqDTO.Item::getCount);
List<ProductSkuRespDTO> skus = productSkuApi.getSkuList(skuIdCountMap.keySet());
// 校验商品 SKU
skus.forEach(sku -> {
Integer count = skuIdCountMap.get(sku.getId());
if (count == null) {
throw exception(SKU_NOT_EXISTS);
}
if (count > sku.getStock()) {
throw exception(SKU_STOCK_NOT_ENOUGH);
}
});
return skus;
}
}

View File

@ -1,25 +1,34 @@
package cn.iocoder.yudao.module.product.api.sku; package cn.iocoder.yudao.module.product.api.sku;
import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO; import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO;
import cn.iocoder.yudao.module.product.api.sku.dto.SkuInfoRespDTO; import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
/** /**
* SKU API
*
* @author LeeYan9 * @author LeeYan9
* @since 2022-08-26 * @since 2022-08-26
*/ */
public interface ProductSkuApi { public interface ProductSkuApi {
/**
* SKU
*
* @param id SKU
* @return SKU
*/
ProductSkuRespDTO getSku(Long id);
/** /**
* skuId sku * SKU
* *
* @param skuIds sku ID * @param ids SKU
* @return sku * @return SKU
*/ */
List<SkuInfoRespDTO> getSkusByIds(Collection<Long> skuIds); List<ProductSkuRespDTO> getSkuList(Collection<Long> ids);
/** /**
* sku * sku
@ -27,4 +36,5 @@ public interface ProductSkuApi {
* @param batchReqDTO sku * @param batchReqDTO sku
*/ */
void decrementStockBatch(SkuDecrementStockBatchReqDTO batchReqDTO); void decrementStockBatch(SkuDecrementStockBatchReqDTO batchReqDTO);
} }

View File

@ -6,11 +6,13 @@ import lombok.Data;
import java.util.List; import java.util.List;
/** /**
* SKU Response DTO
*
* @author LeeYan9 * @author LeeYan9
* @since 2022-08-26 * @since 2022-08-26
*/ */
@Data @Data
public class SkuInfoRespDTO { public class ProductSkuRespDTO {
/** /**
* SKU * SKU

View File

@ -1,23 +1,24 @@
package cn.iocoder.yudao.module.product.api.spu; package cn.iocoder.yudao.module.product.api.spu;
import cn.iocoder.yudao.module.product.api.sku.dto.SkuInfoRespDTO;
import cn.iocoder.yudao.module.product.api.spu.dto.SpuInfoRespDTO; import cn.iocoder.yudao.module.product.api.spu.dto.SpuInfoRespDTO;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
/** /**
* SPU API
*
* @author LeeYan9 * @author LeeYan9
* @since 2022-08-26 * @since 2022-08-26
*/ */
public interface ProductSpuApi { public interface ProductSpuApi {
/** /**
* spuId spu * SPU
* *
* @param spuIds spu ID * @param ids SPU
* @return spu * @return SPU
*/ */
List<SpuInfoRespDTO> getSpusByIds(Collection<Long> spuIds); List<SpuInfoRespDTO> getSpuList(Collection<Long> ids);
} }

View File

@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.product.api.spu.dto; package cn.iocoder.yudao.module.product.api.spu.dto;
import cn.iocoder.yudao.module.product.api.sku.dto.SkuInfoRespDTO; import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.enums.spu.ProductSpuSpecTypeEnum; import cn.iocoder.yudao.module.product.enums.spu.ProductSpuSpecTypeEnum;
import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
import lombok.Data; import lombok.Data;
@ -80,25 +80,25 @@ public class SpuInfoRespDTO {
/** /**
* 使 * 使
* <p> * <p>
* {@link SkuInfoRespDTO#getPrice()} * {@link ProductSkuRespDTO#getPrice()}
*/ */
private Integer minPrice; private Integer minPrice;
/** /**
* 使 * 使
* <p> * <p>
* {@link SkuInfoRespDTO#getPrice()} * {@link ProductSkuRespDTO#getPrice()}
*/ */
private Integer maxPrice; private Integer maxPrice;
/** /**
* 使 * 使
* <p> * <p>
* {@link SkuInfoRespDTO#getMarketPrice()} * {@link ProductSkuRespDTO#getMarketPrice()}
*/ */
private Integer marketPrice; private Integer marketPrice;
/** /**
* *
* <p> * <p>
* {@link SkuInfoRespDTO#getStock()} * {@link ProductSkuRespDTO#getStock()}
*/ */
private Integer totalStock; private Integer totalStock;
/** /**

View File

@ -36,5 +36,6 @@ public interface ErrorCodeConstants {
ErrorCode SKU_PROPERTIES_DUPLICATED = new ErrorCode(1008006001, "商品 SKU 的属性组合存在重复"); ErrorCode SKU_PROPERTIES_DUPLICATED = new ErrorCode(1008006001, "商品 SKU 的属性组合存在重复");
ErrorCode SPU_ATTR_NUMBERS_MUST_BE_EQUALS = new ErrorCode(1008006002, "一个 SPU 下的每个 SKU其规格数必须一致"); ErrorCode SPU_ATTR_NUMBERS_MUST_BE_EQUALS = new ErrorCode(1008006002, "一个 SPU 下的每个 SKU其规格数必须一致");
ErrorCode SPU_SKU_NOT_DUPLICATE = new ErrorCode(1008006003, "一个 SPU 下的每个 SKU必须不重复"); ErrorCode SPU_SKU_NOT_DUPLICATE = new ErrorCode(1008006003, "一个 SPU 下的每个 SKU必须不重复");
ErrorCode SKU_STOCK_NOT_ENOUGH = new ErrorCode(1008006004, "商品 SKU 库存不足");
} }

View File

@ -1,21 +1,38 @@
package cn.iocoder.yudao.module.product.api.sku; package cn.iocoder.yudao.module.product.api.sku;
import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO; import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO;
import cn.iocoder.yudao.module.product.api.sku.dto.SkuInfoRespDTO; import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
/** /**
* todo * SKU API
*
* @author
* @since 2022-08-26
*/ */
@Service @Service
public class ProductSkuApiImpl implements ProductSkuApi { public class ProductSkuApiImpl implements ProductSkuApi {
@Resource
private ProductSkuService productSkuService;
@Override @Override
public List<SkuInfoRespDTO> getSkusByIds(Collection<Long> skuIds) { public ProductSkuRespDTO getSku(Long id) {
return null; ProductSkuDO skuDO = productSkuService.getSku(id);
return ProductSkuConvert.INSTANCE.convert02(skuDO);
}
@Override
public List<ProductSkuRespDTO> getSkuList(Collection<Long> ids) {
List<ProductSkuDO> list = productSkuService.getSkuList(ids);
return ProductSkuConvert.INSTANCE.convertList02(list);
} }
@Override @Override

View File

@ -7,13 +7,16 @@ import java.util.Collection;
import java.util.List; import java.util.List;
/** /**
* todo * SPU API
*
* @author LeeYan9
* @since 2022-08-26
*/ */
@Service @Service
public class ProductSpuApiImpl implements ProductSpuApi { public class ProductSpuApiImpl implements ProductSpuApi {
@Override @Override
public List<SpuInfoRespDTO> getSpusByIds(Collection<Long> spuIds) { public List<SpuInfoRespDTO> getSpuList(Collection<Long> spuIds) {
return null; return null;
} }

View File

@ -33,7 +33,7 @@ public class ProductSkuBaseVO {
@ApiModelProperty(value = "条形码", example = "haha") @ApiModelProperty(value = "条形码", example = "haha")
private String barCode; private String barCode;
@ApiModelProperty(value = "图片地址") @ApiModelProperty(value = "图片地址", required = true, example = "https://www.iocoder.cn/xx.png")
@NotNull(message = "图片地址不能为空") @NotNull(message = "图片地址不能为空")
private String picUrl; private String picUrl;

View File

@ -1,10 +1,10 @@
package cn.iocoder.yudao.module.product.convert.sku; package cn.iocoder.yudao.module.product.convert.sku;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO; import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO;
import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuRespVO; import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuRespVO;
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO; import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
import java.util.List; import java.util.List;
@ -21,11 +21,14 @@ public interface ProductSkuConvert {
ProductSkuDO convert(ProductSkuCreateOrUpdateReqVO bean); ProductSkuDO convert(ProductSkuCreateOrUpdateReqVO bean);
@Mapping(source = "properties", target = "properties")
ProductSkuRespVO convert(ProductSkuDO bean); ProductSkuRespVO convert(ProductSkuDO bean);
List<ProductSkuRespVO> convertList(List<ProductSkuDO> list); List<ProductSkuRespVO> convertList(List<ProductSkuDO> list);
List<ProductSkuDO> convertSkuDOList(List<ProductSkuCreateOrUpdateReqVO> list); List<ProductSkuDO> convertSkuDOList(List<ProductSkuCreateOrUpdateReqVO> list);
ProductSkuRespDTO convert02(ProductSkuDO bean);
List<ProductSkuRespDTO> convertList02(List<ProductSkuDO> list);
} }

View File

@ -1,23 +1,23 @@
package cn.iocoder.yudao.module.trade.enums.enums; package cn.iocoder.yudao.module.trade.enums;
import cn.iocoder.yudao.framework.common.exception.ErrorCode; import cn.iocoder.yudao.framework.common.exception.ErrorCode;
/** /**
* Trade-Order Core *
* <p> * 使 1-011-000-000
* Trade-Order 使 1-011-000-000
* *
* @author LeeYan9 * @author LeeYan9
* @since 2022-08-26 * @since 2022-08-26
*/ */
public interface ErrorCodeConstants { public interface ErrorCodeConstants {
/** // ========== Order 模块 1-011-000-000 ==========
* ========== Order 1-011-000-000 ==========
*/
ErrorCode ORDER_SKU_NOT_FOUND = new ErrorCode(1011000001, "商品不存在"); ErrorCode ORDER_SKU_NOT_FOUND = new ErrorCode(1011000001, "商品不存在");
ErrorCode ORDER_SPU_NOT_SALE = new ErrorCode(1011000002, "商品不可售卖"); ErrorCode ORDER_SPU_NOT_SALE = new ErrorCode(1011000002, "商品不可售卖");
ErrorCode ORDER_SKU_NOT_SALE = new ErrorCode(1011000003, "商品Sku不可售卖"); ErrorCode ORDER_SKU_NOT_SALE = new ErrorCode(1011000003, "商品Sku不可售卖");
ErrorCode ORDER_SKU_STOCK_NOT_ENOUGH = new ErrorCode(1011000004, "商品库存不足"); ErrorCode ORDER_SKU_STOCK_NOT_ENOUGH = new ErrorCode(1011000004, "商品库存不足");
// ========== Cart 模块 1-011-001-000 ==========
ErrorCode CARD_ITEM_NOT_FOUND = new ErrorCode(1001001000, "购物车项不存在");
} }

View File

@ -0,0 +1,4 @@
/**
* VO
*/
package cn.iocoder.yudao.module.trade.controller.app.base;

View File

@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.trade.controller.app.base.property;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel("用户 App - 规格 + 规格值 Response VO")
@Data
public class AppProductPropertyValueDetailRespVO {
@ApiModelProperty(value = "属性的编号", required = true, example = "1")
private Long propertyId;
@ApiModelProperty(value = "属性的名称", required = true, example = "颜色")
private String propertyName;
@ApiModelProperty(value = "属性值的编号", required = true, example = "1024")
private Long valueId;
@ApiModelProperty(value = "属性值的名称", required = true, example = "红色")
private String valueName;
}

View File

@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.trade.controller.app.base.sku;
import cn.iocoder.yudao.module.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
* SKU Response VO
*
* @author
*/
@Data
public class AppProductSkuBaseRespVO {
@ApiModelProperty(value = "主键", required = true, example = "1024")
private Long id;
@ApiModelProperty(value = "商品 SKU 名字", required = true, example = "芋道")
private String name;
@ApiModelProperty(value = "图片地址", example = "https://www.iocoder.cn/xx.png")
private String picUrl;
@ApiModelProperty(value = "库存", required = true, example = "1")
private Integer stock;
/**
*
*/
private List<AppProductPropertyValueDetailRespVO> properties;
}

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.trade.controller.app.base.spu;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
* SPU Response VO
*
* @author
*/
@Data
public class AppProductSpuBaseRespVO {
@ApiModelProperty(value = "主键", required = true, example = "1024")
private Long id;
@ApiModelProperty(value = "商品 SPU 名字", required = true, example = "芋道")
private String name;
@ApiModelProperty(value = "商品主图地址", example = "https://www.iocoder.cn/xx.png")
private List<String> picUrls;
}

View File

@ -1,71 +0,0 @@
package cn.iocoder.yudao.module.trade.controller.app.cart;
import io.swagger.annotations.Api;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Api(tags = "购物车 API")
@RestController
@RequestMapping("/cart")
@Validated
public class CartController {
// @Autowired
// private CartManager cartManager;
//
// @PostMapping("add")
// @ApiOperation("添加商品到购物车")
// @ApiImplicitParams({
// @ApiImplicitParam(name = "skuId", value = "商品 SKU 编号", required = true, example = "1"),
// @ApiImplicitParam(name = "quantity", value = "增加数量", required = true, example = "1024")
// })
// @RequiresAuthenticate
// public CommonResult<Boolean> addCartItem(@RequestParam("skuId") Integer skuId,
// @RequestParam("quantity") Integer quantity) {
// cartManager.addCartItem(UserSecurityContextHolder.getUserId(), skuId, quantity);
// return success(true);
// }
//
// @GetMapping("sum-quantity")
// @ApiOperation("查询用户在购物车中的商品数量")
// @RequiresAuthenticate
// public CommonResult<Integer> sumCartItemQuantity() {
// return success(cartManager.sumCartItemQuantity(UserSecurityContextHolder.getUserId()));
// }
//
// @GetMapping("/get-detail")
// @ApiOperation("查询用户的购物车的商品列表")
// @RequiresAuthenticate
// public CommonResult<CartDetailVO> getCartDetail() {
// return success(cartManager.getCartDetail(UserSecurityContextHolder.getUserId()));
// }
//
// @PostMapping("update-quantity")
// @ApiOperation("更新购物车商品数量")
// @ApiImplicitParams({
// @ApiImplicitParam(name = "skuId", value = "商品 SKU 编号", required = true, example = "1"),
// @ApiImplicitParam(name = "quantity", value = "增加数量", required = true, example = "1024")
// })
// @RequiresAuthenticate
// public CommonResult<Boolean> updateCartItemQuantity(@RequestParam("skuId") Integer skuId,
// @RequestParam("quantity") Integer quantity) {
// cartManager.updateCartItemQuantity(UserSecurityContextHolder.getUserId(), skuId, quantity);
// return success(true);
// }
//
// @PostMapping("update-selected")
// @ApiOperation("更新购物车商品是否选中")
// @ApiImplicitParams({
// @ApiImplicitParam(name = "skuIds", value = "商品 SKU 编号数组", required = true, example = "1,3"),
// @ApiImplicitParam(name = "selected", value = "是否选中", required = true, example = "true")
// })
// @RequiresAuthenticate
// public CommonResult<Boolean> updateCartItemSelected(@RequestParam("skuIds") Set<Integer> skuIds,
// @RequestParam("selected") Boolean selected) {
// cartManager.updateCartItemSelected(UserSecurityContextHolder.getUserId(), skuIds, selected);
// // 获得目前购物车明细
// return success(true);
// }
}

View File

@ -0,0 +1,47 @@
### 请求 /trade/cart/add-count 接口 => 成功
POST {{appApi}}/trade/cart/add-count
tenant-id: {{appTenentId}}
Authorization: Bearer {{appToken}}
Content-Type: application/json
{
"skuId": 1,
"count": 1
}
### 请求 /trade/cart/update-count 接口 => 成功
PUT {{appApi}}/trade/cart/update-count
tenant-id: {{appTenentId}}
Authorization: Bearer {{appToken}}
Content-Type: application/json
{
"skuId": 1,
"count": 5
}
### 请求 /trade/cart/update-selected 接口 => 成功
PUT {{appApi}}/trade/cart/update-selected
tenant-id: {{appTenentId}}
Authorization: Bearer {{appToken}}
Content-Type: application/json
{
"skuIds": [1],
"selected": false
}
### 请求 /trade/cart/delete 接口 => 成功
DELETE {{appApi}}/trade/cart/delete?skuIds=1
tenant-id: {{appTenentId}}
Authorization: Bearer {{appToken}}
### 请求 /trade/cart/get-count 接口 => 成功
GET {{appApi}}/trade/cart/get-count
tenant-id: {{appTenentId}}
Authorization: Bearer {{appToken}}
### 请求 /trade/cart/get-detail 接口 => 成功
GET {{appApi}}/trade/cart/get-detail
tenant-id: {{appTenentId}}
Authorization: Bearer {{appToken}}

View File

@ -0,0 +1,84 @@
package cn.iocoder.yudao.module.trade.controller.app.cart;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartDetailRespVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemAddCountReqVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemUpdateCountReqVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemUpdateSelectedReqVO;
import cn.iocoder.yudao.module.trade.service.cart.TradeCartService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Api(tags = "用户 App - 购物车")
@RestController
@RequestMapping("/trade/cart")
@RequiredArgsConstructor
@Validated
@Slf4j
public class TradeCartController {
@Resource
private TradeCartService cartService;
@PostMapping("/add-count")
@ApiOperation("添加商品到购物车")
@PreAuthenticated
public CommonResult<Boolean> addCartItemCount(@Valid @RequestBody AppTradeCartItemAddCountReqVO addCountReqVO) {
cartService.addCartItemCount(getLoginUserId(), addCountReqVO);
return success(true);
}
@PutMapping("update-count")
@ApiOperation("更新购物车商品数量")
@PreAuthenticated
public CommonResult<Boolean> updateCartItemQuantity(@Valid @RequestBody AppTradeCartItemUpdateCountReqVO updateCountReqVO) {
cartService.updateCartItemCount(getLoginUserId(), updateCountReqVO);
return success(true);
}
@PutMapping("update-selected")
@ApiOperation("更新购物车商品是否选中")
@PreAuthenticated
public CommonResult<Boolean> updateCartItemSelected(@Valid @RequestBody AppTradeCartItemUpdateSelectedReqVO updateSelectedReqVO) {
cartService.updateCartItemSelected(getLoginUserId(), updateSelectedReqVO);
// 获得目前购物车明细
return success(true);
}
@DeleteMapping("/delete")
@ApiOperation("删除购物车商品")
@ApiImplicitParam(name = "skuId", value = "商品 SKU 编号的数组", required = true, example = "1024,2048", dataTypeClass = List.class)
@PreAuthenticated
public CommonResult<Boolean> deleteCartItem(@RequestParam("skuIds") List<Long> skuIds) {
cartService.deleteCartItems(getLoginUserId(), skuIds);
return success(true);
}
@GetMapping("get-count")
@ApiOperation("查询用户在购物车中的商品数量")
@PreAuthenticated
public CommonResult<Integer> getCartCount() {
return success(cartService.getCartCount(getLoginUserId()));
}
@GetMapping("/get-detail")
@ApiOperation("查询用户的购物车的详情")
@PreAuthenticated
public CommonResult<AppTradeCartDetailRespVO> getCartDetail() {
return success(cartService.getCartDetail(getLoginUserId()));
}
}

View File

@ -0,0 +1,118 @@
package cn.iocoder.yudao.module.trade.controller.app.cart.vo;
import cn.iocoder.yudao.module.trade.controller.app.base.sku.AppProductSkuBaseRespVO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@ApiModel(value = "用户 App - 用户的购物车明细 Response VO")
@Data
public class AppTradeCartDetailRespVO {
/**
*
*/
private List<ItemGroup> itemGroups;
/**
*
*/
private Order order;
@ApiModel(value = "商品分组", description = "多个商品,参加同一个活动,从而形成分组")
@Data
public static class ItemGroup {
/**
*
*/
private List<Sku> items;
/**
*
*/
private Promotion promotion;
}
@ApiModel(value = "商品 SKU")
@Data
public static class Sku extends AppProductSkuBaseRespVO {
/**
* SPU
*/
private AppProductSkuBaseRespVO spu;
// ========== 购物车相关的字段 ==========
@ApiModelProperty(value = "商品数量", required = true, example = "1")
private Integer count;
@ApiModelProperty(value = "是否选中", required = true, example = "true")
private Boolean selected;
// ========== 价格相关的字段,对应 PriceCalculateRespDTO.OrderItem 的属性 ==========
// TODO 芋艿:后续可以去除一些无用的字段
@ApiModelProperty(value = "商品原价(单)", required = true, example = "100")
private Integer originalPrice;
@ApiModelProperty(value = "商品原价(总)", required = true, example = "200")
private Integer totalOriginalPrice;
@ApiModelProperty(value = "商品级优惠(总)", required = true, example = "300")
private Integer totalPromotionPrice;
@ApiModelProperty(value = "最终购买金额(总)", required = true, example = "400")
private Integer totalPresentPrice;
@ApiModelProperty(value = "最终购买金额(单)", required = true, example = "500")
private Integer presentPrice;
@ApiModelProperty(value = "应付金额(总)", required = true, example = "600")
private Integer totalPayPrice;
// ========== 营销相关的字段 ==========
/**
*
*/
private Promotion promotion;
}
@ApiModel(value = "订单", description = "对应 PriceCalculateRespDTO.Order 类,用于费用(合计)")
@Data
public static class Order {
// TODO 芋艿:后续可以去除一些无用的字段
@ApiModelProperty(value = "商品原价(总)", required = true, example = "100")
private Integer skuOriginalPrice;
@ApiModelProperty(value = "商品优惠(总)", required = true, example = "200")
private Integer skuPromotionPrice;
@ApiModelProperty(value = "订单优惠(总)", required = true, example = "300")
private Integer orderPromotionPrice;
@ApiModelProperty(value = "运费金额", required = true, example = "400")
private Integer deliveryPrice;
@ApiModelProperty(value = "应付金额(总)", required = true, example = "500")
private Integer payPrice;
}
@ApiModel(value = "营销活动", description = "对应 PriceCalculateRespDTO.Promotion 类的属性")
@Data
public static class Promotion {
@ApiModelProperty(value = "营销编号", required = true, example = "1024", notes = "营销活动的编号、优惠劵的编号")
private Long id;
@ApiModelProperty(value = "营销名字", required = true, example = "xx 活动")
private String name;
@ApiModelProperty(value = "营销类型", required = true, example = "1", notes = "参见 PromotionTypeEnum 枚举类")
private Integer type;
// ========== 匹配情况 ==========
@ApiModelProperty(value = "是否满足优惠条件", required = true, example = "true")
private Boolean meet;
@ApiModelProperty(value = "满足条件的提示", required = true, example = "圣诞价:省 150.00 元")
private String meetTip;
}
}

View File

@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.trade.controller.app.cart.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
@ApiModel(value = "用户 App - 购物车添加购物项 Request VO")
@Data
public class AppTradeCartItemAddCountReqVO {
@ApiModelProperty(value = "商品 SKU 编号", required = true,example = "1024")
@NotNull(message = "商品 SKU 编号不能为空")
private Long skuId;
@ApiModelProperty(value = "商品数量", required = true, example = "1", notes = "注意,这是新增数量")
@NotNull(message = "数量不能为空")
@Min(message = "数量必须大于 0", value = 1L)
private Integer count;
}

View File

@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.trade.controller.app.cart.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
@ApiModel(value = "用户 App - 购物车更新数量 Request VO")
@Data
public class AppTradeCartItemUpdateCountReqVO {
@ApiModelProperty(value = "商品 SKU 编号", required = true, example = "1024")
@NotNull(message = "商品 SKU 编号不能为空")
private Long skuId;
@ApiModelProperty(value = "商品数量", required = true, example = "1")
@NotNull(message = "数量不能为空")
@Min(message = "数量必须大于 0", value = 1L)
private Integer count;
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.trade.controller.app.cart.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.util.Collection;
@ApiModel(value = "用户 App - 购物车更新是否选中 Request VO")
@Data
public class AppTradeCartItemUpdateSelectedReqVO {
@ApiModelProperty(value = "商品 SKU 编号列表", required = true, example = "1024,2048")
@NotNull(message = "商品 SKU 编号列表不能为空")
private Collection<Long> skuIds;
@ApiModelProperty(value = "是否选中", required = true, example = "true")
@NotNull(message = "是否选中不能为空")
private Boolean selected;
}

View File

@ -1,211 +0,0 @@
package cn.iocoder.yudao.module.trade.controller.app.cart.vo;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
@ApiModel(value = "用户的购物车明细 Response VO") // TODO 芋艿swagger 文档完善
@Data
@Accessors(chain = true)
public class CartDetailVO {
/**
*
*/
private List<ItemGroup> itemGroups;
/**
*
*/
private Fee fee;
/**
*
*
*
*/
@Data
@Accessors(chain = true)
public static class ItemGroup {
// /**
// * 优惠活动
// */
// private PromotionActivityRespDTO activity; // TODO 芋艿,偷懒
/**
*
*
* 1. null
* 2. Item discountTotal
*/
private Integer activityDiscountTotal;
/**
*
*/
private List<Sku> items;
}
@Data
@Accessors(chain = true)
public static class Sku {
// SKU 自带信息
/**
* sku
*/
private Integer id;
/**
* SPU
*/
private Spu spu;
/**
*
*/
private String picURL;
// /**
// * 规格值数组
// */
// private List<ProductAttrKeyValueRespVO> attrs; // TODO 后面改下
/**
*
*/
private Integer price;
/**
*
*/
private Integer quantity;
// 非 SKU 自带信息
/**
*
*/
private Integer buyQuantity;
/**
*
*/
private Boolean selected;
// /**
// * 优惠活动
// */
// private PromotionActivityRespDTO activity; // TODO 芋艿,偷懒
/**
*
*/
private Integer originPrice;
/**
*
*/
private Integer buyPrice;
/**
*
*/
private Integer presentPrice;
/**
*
*
* {@link #presentTotal}
*/
private Integer buyTotal;
/**
*
*/
private Integer discountTotal;
/**
*
*
* presentPrice * quantity presentTotal
*
* presentPrice = 8.33 quantity = 3 presentTotal 24.99 25
*
*/
private Integer presentTotal;
}
@Data
@Accessors(chain = true)
public static class Spu {
/**
* SPU
*/
private Integer id;
// ========== 基本信息 =========
/**
* SPU
*/
private String name;
/**
*
*/
private Integer cid;
/**
*
*
*
*
* 800*80015
*/
private List<String> picUrls;
}
/**
*
*/
@Data
@Accessors(chain = true)
public static class Fee {
/**
*
*/
private Integer buyTotal;
/**
*
*
*
*/
private Integer discountTotal;
/**
*
*/
private Integer postageTotal;
/**
*
*
* = - +
*/
private Integer presentTotal;
public Fee() {
}
public Fee(Integer buyTotal, Integer discountTotal, Integer postageTotal, Integer presentTotal) {
this.buyTotal = buyTotal;
this.discountTotal = discountTotal;
this.postageTotal = postageTotal;
this.presentTotal = presentTotal;
}
}
/**
* TODO
*/
@Data
@Accessors(chain = true)
public static class Postage {
/**
*
*/
private Integer threshold;
}
}

View File

@ -60,8 +60,8 @@ public class AppTradeOrderController {
@GetMapping("/get") @GetMapping("/get")
@ApiOperation("获得交易订单") @ApiOperation("获得交易订单")
@ApiImplicitParam(name = "tradeOrderId", value = "交易订单编号", required = true) @ApiImplicitParam(name = "id", value = "交易订单编号", required = true, dataTypeClass = Long.class)
public CommonResult<TradeOrderRespVO> getTradeOrder(@RequestParam("tradeOrderId") Integer tradeOrderId) { public CommonResult<TradeOrderRespVO> getTradeOrder(@RequestParam("id") Integer id) {
// return success(tradeOrderService.getTradeOrder(tradeOrderId)); // return success(tradeOrderService.getTradeOrder(tradeOrderId));
return null; return null;
} }

View File

@ -0,0 +1,45 @@
package cn.iocoder.yudao.module.trade.convert.cart;
import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateReqDTO;
import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartDetailRespVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartItemDO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.Collections;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@Mapper
public interface TradeCartConvert {
TradeCartConvert INSTANCE = Mappers.getMapper(TradeCartConvert.class);
default AppTradeCartDetailRespVO buildEmptyAppTradeCartDetailRespVO() {
return new AppTradeCartDetailRespVO().setItemGroups(Collections.emptyList())
.setOrder(new AppTradeCartDetailRespVO.Order().setSkuOriginalPrice(0).setSkuPromotionPrice(0)
.setOrderPromotionPrice(0).setDeliveryPrice(0).setPayPrice(0));
}
default PriceCalculateReqDTO convert(Long userId, List<TradeCartItemDO> cartItems) {
return new PriceCalculateReqDTO().setUserId(userId)
.setItems(convertList(cartItems, cartItem -> new PriceCalculateReqDTO.Item().setSkuId(cartItem.getSkuId())
.setCount(cartItem.getSelected() ? cartItem.getCount() : 0)));
}
// ========== AppTradeCartDetailRespVO 相关 ==========
AppTradeCartDetailRespVO.Promotion convert(PriceCalculateRespDTO.Promotion bean);
@Mappings({
@Mapping(source = "cartItem.count", target = "count")
})
AppTradeCartDetailRespVO.Sku convert(PriceCalculateRespDTO.OrderItem orderItem, TradeCartItemDO cartItem);
AppTradeCartDetailRespVO.Order convert(PriceCalculateRespDTO.Order bean);
}

View File

@ -29,7 +29,7 @@ public interface TradeOrderItemConvert {
@Mapping(source = "tradeOrder.userId", target = "userId"), @Mapping(source = "tradeOrder.userId", target = "userId"),
@Mapping(source = "tradeOrder.orderId", target = "orderId") @Mapping(source = "tradeOrder.orderId", target = "orderId")
}) })
default List<TradeOrderItemDO> convertList(TradeOrderDO tradeOrder, List<PriceCalculateRespDTO.Item> items) { default List<TradeOrderItemDO> convertList(TradeOrderDO tradeOrder, List<PriceCalculateRespDTO.OrderItem> items) {
// TODO @Com: Mapstruct 生成会报错 // TODO @Com: Mapstruct 生成会报错
throw new UnsupportedOperationException("无法实现"); throw new UnsupportedOperationException("无法实现");
} }

View File

@ -7,6 +7,7 @@ import org.mapstruct.factory.Mappers;
import java.util.List; import java.util.List;
// TODO @LeeYan9挪到 OrderConvert 那
/** /**
* @author LeeYan9 * @author LeeYan9
* @since 2022-08-26 * @since 2022-08-26

View File

@ -6,16 +6,16 @@ import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import java.util.Date;
/** /**
* DO * DO
*
* @author
*/ */
@TableName("trade_cart_item") @TableName("trade_cart_item")
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Accessors(chain = true) @Accessors(chain = true)
public class CartItemDO extends BaseDO { public class TradeCartItemDO extends BaseDO {
// ========= 基础字段 BEGIN ========= // ========= 基础字段 BEGIN =========
@ -27,10 +27,6 @@ public class CartItemDO extends BaseDO {
* *
*/ */
private Boolean selected; private Boolean selected;
/**
*
*/
private Date buyTime; // basket_date
// ========= 基础字段 END ========= // ========= 基础字段 END =========
@ -62,7 +58,7 @@ public class CartItemDO extends BaseDO {
/** /**
* *
*/ */
private Integer stock; private Integer count;
// ========= 商品信息 END ========= // ========= 商品信息 END =========

View File

@ -0,0 +1,47 @@
package cn.iocoder.yudao.module.trade.dal.mysql.cart;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartItemDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@Mapper
public interface TradeCartItemMapper extends BaseMapperX<TradeCartItemDO> {
default TradeCartItemDO selectByUserIdAndSkuId(Long userId, Long skuId) {
return selectOne(TradeCartItemDO::getUserId, userId,
TradeCartItemDO::getSkuId, skuId);
}
default List<TradeCartItemDO> selectListByUserIdAndSkuIds(Long userId, Collection<Long> skuIds) {
return selectList(new LambdaQueryWrapper<TradeCartItemDO>().eq(TradeCartItemDO::getUserId, userId)
.in(TradeCartItemDO::getSkuId, skuIds));
}
default void updateByIds(Collection<Long> ids, TradeCartItemDO updateObject) {
update(updateObject, new LambdaQueryWrapper<TradeCartItemDO>().in(TradeCartItemDO::getId, ids));
}
default Integer selectSumByUserId(Long userId) {
// SQL sum 查询
List<Map<String, Object>> result = selectMaps(new QueryWrapper<TradeCartItemDO>()
.select("SUM(count) AS sumCount")
.eq("user_id", userId));
// 获得数量
return CollUtil.isNotEmpty(result) ? MapUtil.getInt(result.get(0), "sumCount") : 0;
}
default List<TradeCartItemDO> selectListByUserId(Long userId, Boolean selected) {
return selectList(new LambdaQueryWrapperX<TradeCartItemDO>().eq(TradeCartItemDO::getUserId, userId)
.eqIfPresent(TradeCartItemDO::getSelected, selected));
}
}

View File

@ -0,0 +1,66 @@
package cn.iocoder.yudao.module.trade.service.cart;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartDetailRespVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemAddCountReqVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemUpdateCountReqVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemUpdateSelectedReqVO;
import javax.validation.Valid;
import java.util.Collection;
/**
* Service
*
* @author
*/
public interface TradeCartService {
/**
*
*
* @param userId
* @param addCountReqVO
*/
void addCartItemCount(Long userId, @Valid AppTradeCartItemAddCountReqVO addCountReqVO);
/**
*
*
* @param userId
* @param updateCountReqVO
*/
void updateCartItemCount(Long userId, AppTradeCartItemUpdateCountReqVO updateCountReqVO);
/**
*
*
* @param userId
* @param updateSelectedReqVO
*/
void updateCartItemSelected(Long userId, AppTradeCartItemUpdateSelectedReqVO updateSelectedReqVO);
/**
*
*
* @param userId
* @param skuIds SKU
*/
void deleteCartItems(Long userId, Collection<Long> skuIds);
/**
*
*
* @param userId
* @return
*/
Integer getCartCount(Long userId);
/**
*
*
* @param userId
* @return
*/
AppTradeCartDetailRespVO getCartDetail(Long userId);
}

View File

@ -0,0 +1,184 @@
package cn.iocoder.yudao.module.trade.service.cart;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.market.api.price.PriceApi;
import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO;
import cn.iocoder.yudao.module.market.enums.common.PromotionLevelEnum;
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartDetailRespVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemAddCountReqVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemUpdateCountReqVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemUpdateSelectedReqVO;
import cn.iocoder.yudao.module.trade.convert.cart.TradeCartConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartItemDO;
import cn.iocoder.yudao.module.trade.dal.mysql.cart.TradeCartItemMapper;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
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.convertMap;
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.CARD_ITEM_NOT_FOUND;
/**
* Service
*
* @author
*/
@Service
@Validated
public class TradeCartServiceImpl implements TradeCartService {
@Resource
private TradeCartItemMapper cartItemMapper;
@Resource
private ProductSkuApi productSkuApi;
@Resource
private PriceApi priceApi;
@Override
public void addCartItemCount(Long userId, AppTradeCartItemAddCountReqVO addCountReqVO) {
Long skuId = addCountReqVO.getSkuId();
Integer count = addCountReqVO.getCount();
// 查询 CartItemDO
TradeCartItemDO tradeItem = cartItemMapper.selectByUserIdAndSkuId(userId, addCountReqVO.getSkuId());
// 存在,则进行数量更新
if (tradeItem != null) {
checkProductSku(skuId, tradeItem.getCount() + count);
cartItemMapper.updateById(new TradeCartItemDO().setId(tradeItem.getId())
.setSelected(true).setCount(tradeItem.getCount() + count));
return;
}
// 不存在,则进行插入
ProductSkuRespDTO sku = checkProductSku(skuId, count);
cartItemMapper.insert(new TradeCartItemDO().setUserId(userId).setSpuId(sku.getSpuId()).setSkuId(sku.getId())
.setSelected(true).setCount(count));
}
@Override
public void updateCartItemCount(Long userId, AppTradeCartItemUpdateCountReqVO updateCountReqVO) {
// 校验 TradeCartItemDO 存在
TradeCartItemDO tradeItem = cartItemMapper.selectByUserIdAndSkuId(userId, updateCountReqVO.getSkuId());
if (tradeItem == null) {
throw exception(CARD_ITEM_NOT_FOUND);
}
// 校验商品 SKU
checkProductSku(updateCountReqVO.getSkuId(), updateCountReqVO.getCount());
// 更新数量
cartItemMapper.updateById(new TradeCartItemDO().setId(tradeItem.getId()).setCount(updateCountReqVO.getCount()));
}
@Override
public void updateCartItemSelected(Long userId, AppTradeCartItemUpdateSelectedReqVO updateSelectedReqVO) {
// 查询 CartItemDO 列表
List<TradeCartItemDO> cartItems = cartItemMapper.selectListByUserIdAndSkuIds(userId, updateSelectedReqVO.getSkuIds());
if (CollUtil.isEmpty(cartItems)) {
return;
}
// 更新选中
cartItemMapper.updateByIds(CollectionUtils.convertList(cartItems, TradeCartItemDO::getId),
new TradeCartItemDO().setSelected(updateSelectedReqVO.getSelected()));
}
/**
*
*
* @param userId
* @param skuIds SKU
*/
@Override
public void deleteCartItems(Long userId, Collection<Long> skuIds) {
// 查询 CartItemDO 列表
List<TradeCartItemDO> cartItems = cartItemMapper.selectListByUserIdAndSkuIds(userId, skuIds);
if (CollUtil.isEmpty(cartItems)) {
return;
}
// 批量标记删除
cartItemMapper.deleteBatchIds(CollectionUtils.convertSet(cartItems, TradeCartItemDO::getId));
}
@Override
public Integer getCartCount(Long userId) {
return cartItemMapper.selectSumByUserId(userId);
}
@Override
public AppTradeCartDetailRespVO getCartDetail(Long userId) {
// 获得购物车的商品
List<TradeCartItemDO> cartItems = cartItemMapper.selectListByUserId(userId, null);
// 如果未空,则返回空结果
if (CollUtil.isEmpty(cartItems)) {
return TradeCartConvert.INSTANCE.buildEmptyAppTradeCartDetailRespVO();
}
// 调用价格服务,计算价格
PriceCalculateRespDTO priceCalculate = priceApi.calculatePrice(TradeCartConvert.INSTANCE.convert(userId, cartItems));
// 转换返回
Map<Long, TradeCartItemDO> cartItemMap = convertMap(cartItems, TradeCartItemDO::getSkuId);
Map<Long, PriceCalculateRespDTO.OrderItem> orderItemMap = convertMap(priceCalculate.getOrder().getItems(),
PriceCalculateRespDTO.OrderItem::getSkuId);
List<AppTradeCartDetailRespVO.ItemGroup> itemGroups = new ArrayList<>(cartItems.size());
// ① 场景一,营销活动,订单级别 TODO 芋艿:待测试
priceCalculate.getPromotions().stream().filter(promotion -> PromotionLevelEnum.ORDER.getLevel().equals(promotion.getLevel()))
.forEach(promotion -> {
AppTradeCartDetailRespVO.ItemGroup itemGroup = new AppTradeCartDetailRespVO.ItemGroup().setItems(new ArrayList<>())
.setPromotion(TradeCartConvert.INSTANCE.convert(promotion));
itemGroups.add(itemGroup);
promotion.getItems().forEach(promotionItem -> {
PriceCalculateRespDTO.OrderItem orderItem = orderItemMap.remove(promotionItem.getSkuId());
Assert.notNull(orderItem, "商品 SKU({}) 对应的订单项不能为空", promotionItem.getSkuId());
TradeCartItemDO cartItem = cartItemMap.get(orderItem.getSkuId());
itemGroup.getItems().add(TradeCartConvert.INSTANCE.convert(orderItem, cartItem)); // TODO spu
});
});
// ② 场景二,营销活动,商品级别
orderItemMap.values().forEach(orderItem -> {
AppTradeCartDetailRespVO.ItemGroup itemGroup = new AppTradeCartDetailRespVO.ItemGroup().setItems(new ArrayList<>(1)).setPromotion(null);
itemGroups.add(itemGroup);
TradeCartItemDO cartItem = cartItemMap.get(orderItem.getSkuId());
itemGroup.getItems().add(TradeCartConvert.INSTANCE.convert(orderItem, cartItem)); // TODO spu
});
return new AppTradeCartDetailRespVO().setItemGroups(itemGroups)
.setOrder(TradeCartConvert.INSTANCE.convert(priceCalculate.getOrder()));
}
/**
* SKU
* 1.
* 2.
* 3.
*
* @param skuId SKU
* @param count
* @return SKU
*/
private ProductSkuRespDTO checkProductSku(Long skuId, Integer count) {
ProductSkuRespDTO sku = productSkuApi.getSku(skuId);
if (sku == null || CommonStatusEnum.DISABLE.getStatus().equals(sku.getStatus())) {
throw exception(SKU_NOT_EXISTS);
}
if (count > sku.getStock()) {
throw exception(SKU_STOCK_NOT_ENOUGH);
}
return sku;
}
}

View File

@ -11,7 +11,7 @@ import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
import cn.iocoder.yudao.module.pay.api.order.PayOrderDataCreateReqDTO; import cn.iocoder.yudao.module.pay.api.order.PayOrderDataCreateReqDTO;
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO; import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO;
import cn.iocoder.yudao.module.product.api.sku.dto.SkuInfoRespDTO; 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.ProductSpuApi;
import cn.iocoder.yudao.module.product.api.spu.dto.SpuInfoRespDTO; import cn.iocoder.yudao.module.product.api.spu.dto.SpuInfoRespDTO;
import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
@ -26,7 +26,7 @@ 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.dal.dataobject.order.TradeOrderItemDO;
import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper; import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;
import cn.iocoder.yudao.module.trade.dal.mysql.orderitem.TradeOrderItemMapper; import cn.iocoder.yudao.module.trade.dal.mysql.orderitem.TradeOrderItemMapper;
import cn.iocoder.yudao.module.trade.enums.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -62,12 +62,12 @@ public class TradeOrderServiceImpl implements TradeOrderService {
List<Item> items = createReqVO.getItems(); List<Item> items = createReqVO.getItems();
// 商品SKU检查 sku可售状态,库存 // 商品SKU检查 sku可售状态,库存
List<SkuInfoRespDTO> skuInfos = productSkuApi.getSkusByIds(CollectionUtils.convertSet(items, Item::getSkuId)); List<ProductSkuRespDTO> skuInfos = productSkuApi.getSkuList(CollectionUtils.convertSet(items, Item::getSkuId));
Map<Long, SkuInfoRespDTO> skuInfoMap = CollectionUtils.convertMap(skuInfos, SkuInfoRespDTO::getId); Map<Long, ProductSkuRespDTO> skuInfoMap = CollectionUtils.convertMap(skuInfos, ProductSkuRespDTO::getId);
checkSaleableAndStockFromSpu(skuInfoMap, items); checkSaleableAndStockFromSpu(skuInfoMap, items);
// 商品SPU检查 sku可售状态,库存 // 商品SPU检查 sku可售状态,库存
List<SpuInfoRespDTO> spuInfos = productSpuApi.getSpusByIds(CollectionUtils.convertSet(skuInfos, SkuInfoRespDTO::getSpuId)); List<SpuInfoRespDTO> spuInfos = productSpuApi.getSpuList(CollectionUtils.convertSet(skuInfos, ProductSkuRespDTO::getSpuId));
checkSaleableFromSpu(spuInfos); checkSaleableFromSpu(spuInfos);
// 价格计算 // 价格计算
@ -79,7 +79,7 @@ public class TradeOrderServiceImpl implements TradeOrderService {
tradeOrderMapper.insert(tradeOrderDO); tradeOrderMapper.insert(tradeOrderDO);
// 订单项信息记录 // 订单项信息记录
List<TradeOrderItemDO> tradeOrderItems = TradeOrderItemConvert.INSTANCE.convertList(tradeOrderDO, priceResp.getItems()); List<TradeOrderItemDO> tradeOrderItems = TradeOrderItemConvert.INSTANCE.convertList(tradeOrderDO, priceResp.getOrder().getItems());
//-填充订单项-SKU信息 //-填充订单项-SKU信息
fillItemsInfoFromSku(tradeOrderItems, skuInfoMap); fillItemsInfoFromSku(tradeOrderItems, skuInfoMap);
tradeOrderItemMapper.insertBatch(tradeOrderItems); tradeOrderItemMapper.insertBatch(tradeOrderItems);
@ -94,10 +94,10 @@ public class TradeOrderServiceImpl implements TradeOrderService {
} }
private void fillItemsInfoFromSku(List<TradeOrderItemDO> tradeOrderItems, private void fillItemsInfoFromSku(List<TradeOrderItemDO> tradeOrderItems,
Map<Long, SkuInfoRespDTO> spuInfos) { Map<Long, ProductSkuRespDTO> spuInfos) {
for (TradeOrderItemDO tradeOrderItem : tradeOrderItems) { for (TradeOrderItemDO tradeOrderItem : tradeOrderItems) {
// 填充SKU信息 // 填充SKU信息
SkuInfoRespDTO skuInfoRespDTO = spuInfos.get(tradeOrderItem.getSkuId()); ProductSkuRespDTO skuInfoRespDTO = spuInfos.get(tradeOrderItem.getSkuId());
tradeOrderItem.setSpuId(skuInfoRespDTO.getSpuId()); tradeOrderItem.setSpuId(skuInfoRespDTO.getSpuId());
tradeOrderItem.setPicUrl(skuInfoRespDTO.getPicUrl()); tradeOrderItem.setPicUrl(skuInfoRespDTO.getPicUrl());
tradeOrderItem.setName(skuInfoRespDTO.getName()); tradeOrderItem.setName(skuInfoRespDTO.getName());
@ -116,14 +116,14 @@ public class TradeOrderServiceImpl implements TradeOrderService {
} }
} }
private void checkSaleableAndStockFromSpu(Map<Long, SkuInfoRespDTO> skuInfoMap, private void checkSaleableAndStockFromSpu(Map<Long, ProductSkuRespDTO> skuInfoMap,
List<Item> items) { List<Item> items) {
// sku 不存在 // sku 不存在
if (items.size() != skuInfoMap.size()) { if (items.size() != skuInfoMap.size()) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.ORDER_SKU_NOT_FOUND); throw ServiceExceptionUtil.exception(ErrorCodeConstants.ORDER_SKU_NOT_FOUND);
} }
for (Item item : items) { for (Item item : items) {
SkuInfoRespDTO skuInfoDTO = skuInfoMap.get(item.getSkuId()); ProductSkuRespDTO skuInfoDTO = skuInfoMap.get(item.getSkuId());
// sku禁用 // sku禁用
if (!Objects.equals(CommonStatusEnum.ENABLE.getStatus(), skuInfoDTO.getStatus())) { if (!Objects.equals(CommonStatusEnum.ENABLE.getStatus(), skuInfoDTO.getStatus())) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.ORDER_SKU_NOT_SALE); throw ServiceExceptionUtil.exception(ErrorCodeConstants.ORDER_SKU_NOT_SALE);

View File

@ -173,6 +173,7 @@ logging:
cn.iocoder.yudao.module.system.dal.mysql: debug cn.iocoder.yudao.module.system.dal.mysql: debug
cn.iocoder.yudao.module.tool.dal.mysql: debug cn.iocoder.yudao.module.tool.dal.mysql: debug
cn.iocoder.yudao.module.member.dal.mysql: debug cn.iocoder.yudao.module.member.dal.mysql: debug
cn.iocoder.yudao.module.trade.dal.mysql: debug
--- #################### 微信公众号、小程序相关配置 #################### --- #################### 微信公众号、小程序相关配置 ####################
wx: wx: