商城:完善购物车的逻辑

pull/2/head
YunaiV 2023-04-30 20:32:01 +08:00
parent 2113e825d7
commit da162853ec
22 changed files with 349 additions and 430 deletions

View File

@ -46,6 +46,18 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2)); return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2));
} }
default T selectOne(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2,
SFunction<T, ?> field3, Object value3) {
return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2)
.eq(field3, value3));
}
default T selectOne(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2,
SFunction<T, ?> field3, Object value3, SFunction<T, ?> field4, Object value4) {
return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2)
.eq(field3, value3).eq(field4, value4));
}
default Long selectCount() { default Long selectCount() {
return selectCount(new QueryWrapper<T>()); return selectCount(new QueryWrapper<T>());
} }

View File

@ -28,7 +28,7 @@ public class ProductSkuRespDTO {
private String spuName; private String spuName;
/** /**
* JSON *
*/ */
private List<Property> properties; private List<Property> properties;
/** /**
@ -84,12 +84,20 @@ public class ProductSkuRespDTO {
* *
*/ */
private Long propertyId; private Long propertyId;
/**
*
*/
private String propertyName;
/** /**
* *
*/ */
private Long valueId; private Long valueId;
/**
*
*/
private String valueName;
} }
} }

View File

@ -29,17 +29,23 @@ public class ProductSpuRespDTO {
*/ */
private String name; private String name;
/** /**
* *
*/ */
private String code; private String keyword;
/** /**
* *
*/ */
private String sellPoint; private String introduction;
/** /**
* *
*/ */
private String description; private String description;
// TODO @芋艿:是不是要删除
/**
*
*/
private String barCode;
/** /**
* *
*/ */
@ -49,13 +55,13 @@ public class ProductSpuRespDTO {
*/ */
private Long brandId; private Long brandId;
/** /**
* *
* <p>
* 1.
* 2. 使 800x800 1M
* 3. 1 10
*/ */
private List<String> picUrls; private String picUrl;
/**
*
*/
private List<String> sliderPicUrls;
/** /**
* *
*/ */
@ -76,38 +82,27 @@ public class ProductSpuRespDTO {
/** /**
* *
* <p> *
* {@link ProductSpuSpecTypeEnum} * false -
* true -
*/ */
private Boolean specType; private Boolean specType;
/** /**
* 使 * 使
* <p>
* {@link ProductSkuRespDTO#getPrice()}
*/ */
private Integer minPrice; private Integer price;
/**
* 使
* <p>
* {@link ProductSkuRespDTO#getPrice()}
*/
private Integer maxPrice;
/** /**
* 使 * 使
* <p>
* {@link ProductSkuRespDTO#getMarketPrice()}
*/ */
private Integer marketPrice; private Integer marketPrice;
/** /**
* * 使
* <p>
* {@link ProductSkuRespDTO#getStock()}
*/ */
private Integer totalStock; private Integer costPrice;
/** /**
* *
*/ */
private Boolean showStock; private Integer stock;
// ========== 统计相关字段 ========= // ========== 统计相关字段 =========

View File

@ -15,7 +15,8 @@ import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
* TODO LeeYan9: ; * SKU API
*
* @author LeeYan9 * @author LeeYan9
* @since 2022-09-06 * @since 2022-09-06
*/ */
@ -28,8 +29,8 @@ public class ProductSkuApiImpl implements ProductSkuApi {
@Override @Override
public ProductSkuRespDTO getSku(Long id) { public ProductSkuRespDTO getSku(Long id) {
// TODO TODO LeeYan9: 需要实现 ProductSkuDO sku = productSkuService.getSku(id);
return null; return ProductSkuConvert.INSTANCE.convert02(sku);
} }
@Override @Override

View File

@ -26,6 +26,9 @@ public class AppProductSpuPageItemRespVO {
// ========== SKU 相关字段 ========= // ========== SKU 相关字段 =========
@Schema(description = "规格类型", required = true, example = "true")
private Boolean specType;
@Schema(description = "商品价格,单位使用:分", required = true, example = "1024") @Schema(description = "商品价格,单位使用:分", required = true, example = "1024")
private Integer price; private Integer price;

View File

@ -109,12 +109,29 @@ public class ProductSkuDO extends BaseDO {
* {@link ProductPropertyDO#getId()} * {@link ProductPropertyDO#getId()}
*/ */
private Long propertyId; private Long propertyId;
/**
*
*
* {@link ProductPropertyDO#getName()}
*
*
*/
private String propertyName;
/** /**
* *
* *
* {@link ProductPropertyValueDO#getId()} * {@link ProductPropertyValueDO#getId()}
*/ */
private Long valueId; private Long valueId;
/**
*
*
* {@link ProductPropertyValueDO#getName()}
*
*
*/
private String valueName;
} }
@ -139,9 +156,5 @@ public class ProductSkuDO extends BaseDO {
// TODO 芋艿pinkStock from y // TODO 芋艿pinkStock from y
// TODO 芋艿seckillStock from y // TODO 芋艿seckillStock from y
// TODO 芋艿quota from c
// TODO 芋艿quotaShow from c
// TODO 芋艿attrValue from c
} }

View File

@ -67,6 +67,7 @@ public class ProductPropertyServiceImpl implements ProductPropertyService {
// 更新 // 更新
ProductPropertyDO updateObj = ProductPropertyConvert.INSTANCE.convert(updateReqVO); ProductPropertyDO updateObj = ProductPropertyConvert.INSTANCE.convert(updateReqVO);
productPropertyMapper.updateById(updateObj); productPropertyMapper.updateById(updateObj);
// TODO 芋艿:更新时,需要看看 sku 表
} }
@Override @Override

View File

@ -68,6 +68,7 @@ public class ProductPropertyValueServiceImpl implements ProductPropertyValueServ
// 更新 // 更新
ProductPropertyValueDO updateObj = ProductPropertyValueConvert.INSTANCE.convert(updateReqVO); ProductPropertyValueDO updateObj = ProductPropertyValueConvert.INSTANCE.convert(updateReqVO);
productPropertyValueMapper.updateById(updateObj); productPropertyValueMapper.updateById(updateObj);
// TODO 芋艿:更新时,需要看看 sku 表
} }
@Override @Override

View File

@ -54,12 +54,14 @@ public class ProductSkuServiceTest extends BaseDbUnitTest {
// mock 数据 // mock 数据
ProductSkuDO sku01 = randomPojo(ProductSkuDO.class, o -> { // 测试更新 ProductSkuDO sku01 = randomPojo(ProductSkuDO.class, o -> { // 测试更新
o.setSpuId(1L); o.setSpuId(1L);
o.setProperties(singletonList(new ProductSkuDO.Property(10L, 20L))); o.setProperties(singletonList(new ProductSkuDO.Property(
10L, "颜色", 20L, "红色")));
}); });
productSkuMapper.insert(sku01); productSkuMapper.insert(sku01);
ProductSkuDO sku02 = randomPojo(ProductSkuDO.class, o -> { // 测试删除 ProductSkuDO sku02 = randomPojo(ProductSkuDO.class, o -> { // 测试删除
o.setSpuId(1L); o.setSpuId(1L);
o.setProperties(singletonList(new ProductSkuDO.Property(10L, 30L))); o.setProperties(singletonList(new ProductSkuDO.Property(
10L, "颜色", 30L, "蓝色")));
}); });
productSkuMapper.insert(sku02); productSkuMapper.insert(sku02);
// 准备参数 // 准备参数

View File

@ -20,6 +20,6 @@ public class AppProductSpuBaseRespVO {
private String name; private String name;
@Schema(description = "商品主图地址", example = "https://www.iocoder.cn/xx.png") @Schema(description = "商品主图地址", example = "https://www.iocoder.cn/xx.png")
private List<String> picUrls; private String picUrl;
} }

View File

@ -1,38 +1,28 @@
### 请求 /trade/cart/add-count 接口 => 成功 ### 请求 /trade/cart/add 接口 => 成功
POST {{appApi}}/trade/cart/add-count POST {{appApi}}/trade/cart/add
tenant-id: {{appTenentId}} tenant-id: {{appTenentId}}
Authorization: Bearer {{appToken}} Authorization: Bearer {{appToken}}
Content-Type: application/json Content-Type: application/json
{ {
"skuId": 1, "skuId": 1,
"count": 1 "count": 10,
"addStatus": true
} }
### 请求 /trade/cart/update-count 接口 => 成功 ### 请求 /trade/cart/update 接口 => 成功
PUT {{appApi}}/trade/cart/update-count PUT {{appApi}}/trade/cart/update
tenant-id: {{appTenentId}} tenant-id: {{appTenentId}}
Authorization: Bearer {{appToken}} Authorization: Bearer {{appToken}}
Content-Type: application/json Content-Type: application/json
{ {
"skuId": 1, "id": 35,
"count": 5 "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 接口 => 成功 ### 请求 /trade/cart/delete 接口 => 成功
DELETE {{appApi}}/trade/cart/delete?skuIds=1 DELETE {{appApi}}/trade/cart/delete?ids=1
tenant-id: {{appTenentId}} tenant-id: {{appTenentId}}
Authorization: Bearer {{appToken}} Authorization: Bearer {{appToken}}
@ -41,7 +31,7 @@ GET {{appApi}}/trade/cart/get-count
tenant-id: {{appTenentId}} tenant-id: {{appTenentId}}
Authorization: Bearer {{appToken}} Authorization: Bearer {{appToken}}
### 请求 /trade/cart/get-detail 接口 => 成功 ### 请求 /trade/cart/list 接口 => 成功
GET {{appApi}}/trade/cart/get-detail GET {{appApi}}/trade/cart/list
tenant-id: {{appTenentId}} tenant-id: {{appTenentId}}
Authorization: Bearer {{appToken}} Authorization: Bearer {{appToken}}

View File

@ -2,10 +2,9 @@ package cn.iocoder.yudao.module.trade.controller.app.cart;
import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; 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.AppTradeCartListRespVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemAddCountReqVO; import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartAddReqVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemUpdateCountReqVO; import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartUpdateReqVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemUpdateSelectedReqVO;
import cn.iocoder.yudao.module.trade.service.cart.TradeCartService; import cn.iocoder.yudao.module.trade.service.cart.TradeCartService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
@ -33,37 +32,27 @@ public class TradeCartController {
@Resource @Resource
private TradeCartService cartService; private TradeCartService cartService;
@PostMapping("/add-count") @PostMapping("/add")
@Operation(summary = "添加商品到购物车") @Operation(summary = "添加购物车商品")
@PreAuthenticated @PreAuthenticated
public CommonResult<Boolean> addCartItemCount(@Valid @RequestBody AppTradeCartItemAddCountReqVO addCountReqVO) { public CommonResult<Long> addCart(@Valid @RequestBody AppTradeCartAddReqVO addCountReqVO) {
cartService.addCartItemCount(getLoginUserId(), addCountReqVO); return success(cartService.addCart(getLoginUserId(), addCountReqVO));
return success(true);
} }
@PutMapping("update-count") @PutMapping("/update")
@Operation(summary = "更新购物车商品数量") @Operation(summary = "更新购物车商品")
@PreAuthenticated @PreAuthenticated
public CommonResult<Boolean> updateCartItemQuantity(@Valid @RequestBody AppTradeCartItemUpdateCountReqVO updateCountReqVO) { public CommonResult<Boolean> updateCartItemQuantity(@Valid @RequestBody AppTradeCartUpdateReqVO updateReqVO) {
cartService.updateCartItemCount(getLoginUserId(), updateCountReqVO); cartService.updateCart(getLoginUserId(), updateReqVO);
return success(true);
}
@PutMapping("update-selected")
@Operation(summary = "更新购物车商品是否选中")
@PreAuthenticated
public CommonResult<Boolean> updateCartItemSelected(@Valid @RequestBody AppTradeCartItemUpdateSelectedReqVO updateSelectedReqVO) {
cartService.updateCartItemSelected(getLoginUserId(), updateSelectedReqVO);
// 获得目前购物车明细
return success(true); return success(true);
} }
@DeleteMapping("/delete") @DeleteMapping("/delete")
@Operation(summary = "删除购物车商品") @Operation(summary = "删除购物车商品")
@Parameter(name = "skuIds", description = "商品 SKU 编号的数组", required = true, example = "1024,2048") @Parameter(name = "ids", description = "购物车商品编号", required = true, example = "1024,2048")
@PreAuthenticated @PreAuthenticated
public CommonResult<Boolean> deleteCartItem(@RequestParam("skuIds") List<Long> skuIds) { public CommonResult<Boolean> deleteCart(@RequestParam("ids") List<Long> ids) {
cartService.deleteCartItems(getLoginUserId(), skuIds); cartService.deleteCart(getLoginUserId(), ids);
return success(true); return success(true);
} }
@ -74,11 +63,11 @@ public class TradeCartController {
return success(cartService.getCartCount(getLoginUserId())); return success(cartService.getCartCount(getLoginUserId()));
} }
@GetMapping("/get-detail") @GetMapping("/list")
@Operation(summary = "查询用户的购物车的详情") @Operation(summary = "查询用户的购物车列表")
@PreAuthenticated @PreAuthenticated
public CommonResult<AppTradeCartDetailRespVO> getCartDetail() { public CommonResult<AppTradeCartListRespVO> getCartList() {
return success(cartService.getCartDetail(getLoginUserId())); return success(cartService.getCartList(getLoginUserId()));
} }
} }

View File

@ -8,7 +8,7 @@ import javax.validation.constraints.NotNull;
@Schema(description = "用户 App - 购物车添加购物项 Request VO") @Schema(description = "用户 App - 购物车添加购物项 Request VO")
@Data @Data
public class AppTradeCartItemAddCountReqVO { public class AppTradeCartAddReqVO {
@Schema(description = "商品 SKU 编号", required = true,example = "1024") @Schema(description = "商品 SKU 编号", required = true,example = "1024")
@NotNull(message = "商品 SKU 编号不能为空") @NotNull(message = "商品 SKU 编号不能为空")
@ -19,4 +19,8 @@ public class AppTradeCartItemAddCountReqVO {
@Min(message = "数量必须大于 0", value = 1L) @Min(message = "数量必须大于 0", value = 1L)
private Integer count; private Integer count;
@Schema(description = "是否添加到购物车", required = true, example = "true")
@NotNull(message = "是否添加购物车不能为空")
private Boolean addStatus;
} }

View File

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

View File

@ -0,0 +1,45 @@
package cn.iocoder.yudao.module.trade.controller.app.cart.vo;
import cn.iocoder.yudao.module.trade.controller.app.base.sku.AppProductSkuBaseRespVO;
import cn.iocoder.yudao.module.trade.controller.app.base.spu.AppProductSpuBaseRespVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Schema(description = "用户 App - 用户的购物列表 Response VO")
@Data
public class AppTradeCartListRespVO {
/**
*
*/
private List<Cart> validList;
/**
*
*/
private List<Cart> invalidList;
@Schema(description = "购物项")
@Data
public static class Cart {
@Schema(description = "购物项的编号", required = true, example = "1024")
private Long id;
@Schema(description = "商品数量", required = true, example = "1")
private Integer count;
/**
* SPU
*/
private AppProductSpuBaseRespVO spu;
/**
* SKU
*/
private AppProductSkuBaseRespVO sku;
}
}

View File

@ -6,13 +6,13 @@ import lombok.Data;
import javax.validation.constraints.Min; import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
@Schema(description = "用户 App - 购物车更新数量 Request VO") @Schema(description = "用户 App - 购物车更新 Request VO")
@Data @Data
public class AppTradeCartItemUpdateCountReqVO { public class AppTradeCartUpdateReqVO {
@Schema(description = "商品 SKU 编号", required = true, example = "1024") @Schema(description = "编号", required = true, example = "1024")
@NotNull(message = "商品 SKU 编号不能为空") @NotNull(message = "编号不能为空")
private Long skuId; private Long id;
@Schema(description = "商品数量", required = true, example = "1") @Schema(description = "商品数量", required = true, example = "1")
@NotNull(message = "数量不能为空") @NotNull(message = "数量不能为空")

View File

@ -1,45 +1,52 @@
package cn.iocoder.yudao.module.trade.convert.cart; package cn.iocoder.yudao.module.trade.convert.cart;
import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateReqDTO; import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO; import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartDetailRespVO; import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartItemDO; import cn.iocoder.yudao.module.trade.controller.app.base.sku.AppProductSkuBaseRespVO;
import cn.iocoder.yudao.module.trade.controller.app.base.spu.AppProductSpuBaseRespVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartListRespVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartDO;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
import java.util.Collections; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
@Mapper @Mapper
public interface TradeCartConvert { public interface TradeCartConvert {
TradeCartConvert INSTANCE = Mappers.getMapper(TradeCartConvert.class); TradeCartConvert INSTANCE = Mappers.getMapper(TradeCartConvert.class);
default AppTradeCartDetailRespVO buildEmptyAppTradeCartDetailRespVO() { default AppTradeCartListRespVO convertList(List<TradeCartDO> carts,
return new AppTradeCartDetailRespVO().setItemGroups(Collections.emptyList()) List<ProductSpuRespDTO> spus, List<ProductSkuRespDTO> skus) {
.setOrder(new AppTradeCartDetailRespVO.Order().setSkuOriginalPrice(0).setSkuPromotionPrice(0) Map<Long, ProductSpuRespDTO> spuMap = convertMap(spus, ProductSpuRespDTO::getId);
.setOrderPromotionPrice(0).setDeliveryPrice(0).setPayPrice(0)); Map<Long, ProductSkuRespDTO> skuMap = convertMap(skus, ProductSkuRespDTO::getId);
// 遍历,开始转换
List<AppTradeCartListRespVO.Cart> validList = new ArrayList<>(carts.size());
List<AppTradeCartListRespVO.Cart> invalidList = new ArrayList<>();
carts.forEach(cart -> {
AppTradeCartListRespVO.Cart cartVO = new AppTradeCartListRespVO.Cart();
cartVO.setId(cart.getId()).setCount(cart.getCount());
ProductSpuRespDTO spu = spuMap.get(cart.getSpuId());
ProductSkuRespDTO sku = skuMap.get(cart.getSkuId());
cartVO.setSpu(convert(spu)).setSku(convert(sku));
// 如果 spu 或 sku 不存在,或者 spu 被禁用,说明是非法的,或者 sku 库存不足
if (spu == null
|| sku == null
|| !ProductSpuStatusEnum.isEnable(spu.getStatus())
|| sku.getStock() <= 0) {
invalidList.add(cartVO);
} else {
validList.add(cartVO);
} }
});
default PriceCalculateReqDTO convert(Long userId, List<TradeCartItemDO> cartItems) { return new AppTradeCartListRespVO().setValidList(validList).setValidList(invalidList);
return new PriceCalculateReqDTO().setUserId(userId)
.setItems(convertList(cartItems, cartItem -> new PriceCalculateReqDTO.Item().setSkuId(cartItem.getSkuId())
.setCount(cartItem.getSelected() ? cartItem.getCount() : 0)));
} }
AppProductSpuBaseRespVO convert(ProductSpuRespDTO spu);
// ========== AppTradeCartDetailRespVO 相关 ========== AppProductSkuBaseRespVO convert(ProductSkuRespDTO sku);
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

@ -9,13 +9,15 @@ import lombok.experimental.Accessors;
/** /**
* DO * DO
* *
* {@link #spuId} {@link #skuId}
*
* @author * @author
*/ */
@TableName("trade_cart_item") @TableName("trade_cart")
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Accessors(chain = true) @Accessors(chain = true)
public class TradeCartItemDO extends BaseDO { public class TradeCartDO extends BaseDO {
// ========= 基础字段 BEGIN ========= // ========= 基础字段 BEGIN =========
@ -23,14 +25,6 @@ public class TradeCartItemDO extends BaseDO {
* *
*/ */
private Long id; private Long id;
/**
*
*/
private Boolean selected;
// ========= 基础字段 END =========
// ========= 买家信息 BEGIN =========
/** /**
* *
@ -39,7 +33,25 @@ public class TradeCartItemDO extends BaseDO {
*/ */
private Long userId; private Long userId;
// ========= 买家信息 END ========= /**
*
*
* false -
* true -
*
*
* orderStatus
*/
private Boolean addStatus;
/**
*
*
* false - false
* true - true
*/
private Boolean orderStatus;
// ========= 基础字段 END =========
// ========= 商品信息 BEGIN ========= // ========= 商品信息 BEGIN =========
@ -64,27 +76,11 @@ public class TradeCartItemDO extends BaseDO {
// ========= 优惠信息 BEGIN ========= // ========= 优惠信息 BEGIN =========
// /**
// * 商品营销活动编号
// */
// private Long activityId; // discount_id
// /**
// * 商品营销活动类型
// */
// private Integer activityType;
// TODO 芋艿combination_id 拼团 ID // TODO 芋艿combination_id 拼团 ID
// TODO 芋艿seckill_id 秒杀产品 ID // TODO 芋艿seckill_id 秒杀产品 ID
// TODO 芋艿bargain_id 砍价 ID // TODO 芋艿bargain_id 砍价 ID
// TODO 芋艿pinkId 团长拼团 ID
// ========= 优惠信息 END ========= // ========= 优惠信息 END =========
// TODO 待确定字段mf
// TODO 芋艿distribution_card_no 推广员
// TODO 芋艿is_pay 未购买、已购买
// TODO 芋艿is_new 是否立即购买
// TODO 待确定字段: yv
// TODO isPay: 是否购买
// TODO isNew是否立即购买
} }

View File

@ -1,47 +0,0 @@
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,56 @@
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.module.trade.dal.dataobject.cart.TradeCartDO;
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 TradeCartMapper extends BaseMapperX<TradeCartDO> {
default TradeCartDO selectByUserIdAndSkuId(Long userId, Long skuId,
Boolean addStatus, Boolean orderStatus) {
return selectOne(TradeCartDO::getUserId, userId,
TradeCartDO::getSkuId, skuId,
TradeCartDO::getAddStatus, addStatus,
TradeCartDO::getOrderStatus, orderStatus);
}
default Integer selectSumByUserId(Long userId) {
// SQL sum 查询
List<Map<String, Object>> result = selectMaps(new QueryWrapper<TradeCartDO>()
.select("SUM(count) AS sumCount")
.eq("user_id", userId)
.eq("add_status", true) // 只计算添加到购物车中的
.eq("order_status", false)); // 必须未下单
// 获得数量
return CollUtil.getFirst(result) != null ? MapUtil.getInt(result.get(0), "sumCount") : 0;
}
default TradeCartDO selectById(Long id, Long userId) {
return selectOne(TradeCartDO::getId, id,
TradeCartDO::getUserId, userId);
}
default List<TradeCartDO> selectListByIds(Collection<Long> ids, Long userId) {
return selectList(new LambdaQueryWrapper<TradeCartDO>()
.in(TradeCartDO::getId, ids)
.eq(TradeCartDO::getUserId, userId));
}
default List<TradeCartDO> selectListByUserId(Long userId) {
return selectList(TradeCartDO::getUserId, userId);
}
default void updateByIds(Collection<Long> ids, TradeCartDO updateObject) {
update(updateObject, new LambdaQueryWrapper<TradeCartDO>().in(TradeCartDO::getId, ids));
}
}

View File

@ -1,9 +1,8 @@
package cn.iocoder.yudao.module.trade.service.cart; 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.AppTradeCartListRespVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemAddCountReqVO; import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartAddReqVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemUpdateCountReqVO; import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartUpdateReqVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemUpdateSelectedReqVO;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.Collection; import java.util.Collection;
@ -19,9 +18,10 @@ public interface TradeCartService {
* *
* *
* @param userId * @param userId
* @param addCountReqVO * @param addReqVO
* @return
*/ */
void addCartItemCount(Long userId, @Valid AppTradeCartItemAddCountReqVO addCountReqVO); Long addCart(Long userId, @Valid AppTradeCartAddReqVO addReqVO);
/** /**
* *
@ -29,23 +29,15 @@ public interface TradeCartService {
* @param userId * @param userId
* @param updateCountReqVO * @param updateCountReqVO
*/ */
void updateCartItemCount(Long userId, AppTradeCartItemUpdateCountReqVO updateCountReqVO); void updateCart(Long userId, AppTradeCartUpdateReqVO updateCountReqVO);
/**
*
*
* @param userId
* @param updateSelectedReqVO
*/
void updateCartItemSelected(Long userId, AppTradeCartItemUpdateSelectedReqVO updateSelectedReqVO);
/** /**
* *
* *
* @param userId * @param userId
* @param skuIds SKU * @param ids
*/ */
void deleteCartItems(Long userId, Collection<Long> skuIds); void deleteCart(Long userId, Collection<Long> ids);
/** /**
* *
@ -56,11 +48,11 @@ public interface TradeCartService {
Integer getCartCount(Long userId); Integer getCartCount(Long userId);
/** /**
* *
* *
* @param userId * @param userId
* @return * @return
*/ */
AppTradeCartDetailRespVO getCartDetail(Long userId); AppTradeCartListRespVO getCartList(Long userId);
} }

View File

@ -1,39 +1,38 @@
package cn.iocoder.yudao.module.trade.service.cart; package cn.iocoder.yudao.module.trade.service.cart;
import cn.hutool.core.collection.CollUtil; 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.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.promotion.api.price.PriceApi;
import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionLevelEnum;
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.ProductSkuRespDTO; 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.product.api.spu.ProductSpuApi;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemAddCountReqVO; import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemUpdateCountReqVO; import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartAddReqVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemUpdateSelectedReqVO; import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartListRespVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartUpdateReqVO;
import cn.iocoder.yudao.module.trade.convert.cart.TradeCartConvert; 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.dataobject.cart.TradeCartDO;
import cn.iocoder.yudao.module.trade.dal.mysql.cart.TradeCartItemMapper; import cn.iocoder.yudao.module.trade.dal.mysql.cart.TradeCartMapper;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator;
import java.util.List; 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.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS; 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.product.enums.ErrorCodeConstants.SKU_STOCK_NOT_ENOUGH;
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.CARD_ITEM_NOT_FOUND; import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.CARD_ITEM_NOT_FOUND;
import static java.util.Collections.emptyList;
/** /**
* Service * Service
* *
* // TODO 芋艿:秒杀、拼团、砍价对购物车的影响
* // TODO 芋艿:未来优化:购物车的价格计算,支持营销信息
*
* @author * @author
*/ */
@Service @Service
@ -41,123 +40,92 @@ import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.CARD_ITEM_N
public class TradeCartServiceImpl implements TradeCartService { public class TradeCartServiceImpl implements TradeCartService {
@Resource @Resource
private TradeCartItemMapper cartItemMapper; private TradeCartMapper cartMapper;
@Resource
private ProductSpuApi productSpuApi;
@Resource @Resource
private ProductSkuApi productSkuApi; private ProductSkuApi productSkuApi;
@Resource
private PriceApi priceApi;
@Override @Override
public void addCartItemCount(Long userId, AppTradeCartItemAddCountReqVO addCountReqVO) { public Long addCart(Long userId, AppTradeCartAddReqVO addReqVO) {
Long skuId = addCountReqVO.getSkuId(); // 查询 TradeCartDO
Integer count = addCountReqVO.getCount(); TradeCartDO cart = cartMapper.selectByUserIdAndSkuId(userId, addReqVO.getSkuId(),
// 查询 CartItemDO addReqVO.getAddStatus(), false);
TradeCartItemDO tradeItem = cartItemMapper.selectByUserIdAndSkuId(userId, addCountReqVO.getSkuId()); // 校验 SKU
Integer count = cart != null && addReqVO.getAddStatus() ?
cart.getCount() + addReqVO.getCount() : addReqVO.getCount();
ProductSkuRespDTO sku = checkProductSku(addReqVO.getSkuId(), count);
// 存在,则进行数量更新 // 情况一:存在,则进行数量更新
if (tradeItem != null) { if (cart != null) {
checkProductSku(skuId, tradeItem.getCount() + count); cartMapper.updateById(new TradeCartDO().setId(cart.getId()).setCount(count));
cartItemMapper.updateById(new TradeCartItemDO().setId(tradeItem.getId()) return cart.getId();
.setSelected(true).setCount(tradeItem.getCount() + count)); // 情况二:不存在,则进行插入
return; } else {
cart = new TradeCartDO().setUserId(userId)
.setSpuId(sku.getSpuId()).setSkuId(sku.getId()).setCount(count)
.setAddStatus(addReqVO.getAddStatus()).setOrderStatus(false);
cartMapper.insert(cart);
} }
return cart.getId();
// 不存在,则进行插入
ProductSkuRespDTO sku = checkProductSku(skuId, count);
cartItemMapper.insert(new TradeCartItemDO().setUserId(userId).setSpuId(sku.getSpuId()).setSkuId(sku.getId())
.setSelected(true).setCount(count));
} }
@Override @Override
public void updateCartItemCount(Long userId, AppTradeCartItemUpdateCountReqVO updateCountReqVO) { public void updateCart(Long userId, AppTradeCartUpdateReqVO updateReqVO) {
// 校验 TradeCartItemDO 存在 // 校验 TradeCartDO 存在
TradeCartItemDO tradeItem = cartItemMapper.selectByUserIdAndSkuId(userId, updateCountReqVO.getSkuId()); TradeCartDO cart = cartMapper.selectById(updateReqVO.getId(), userId);
if (tradeItem == null) { if (cart == null) {
throw exception(CARD_ITEM_NOT_FOUND); throw exception(CARD_ITEM_NOT_FOUND);
} }
// 校验商品 SKU // 校验商品 SKU
checkProductSku(updateCountReqVO.getSkuId(), updateCountReqVO.getCount()); checkProductSku(cart.getSkuId(), updateReqVO.getCount());
// 更新数量 // 更新数量
cartItemMapper.updateById(new TradeCartItemDO().setId(tradeItem.getId()).setCount(updateCountReqVO.getCount())); cartMapper.updateById(new TradeCartDO().setId(cart.getId())
} .setCount(updateReqVO.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 userId
* @param skuIds SKU * @param ids SKU
*/ */
@Override @Override
public void deleteCartItems(Long userId, Collection<Long> skuIds) { public void deleteCart(Long userId, Collection<Long> ids) {
// 查询 CartItemDO 列表 // 查询 TradeCartDO 列表
List<TradeCartItemDO> cartItems = cartItemMapper.selectListByUserIdAndSkuIds(userId, skuIds); List<TradeCartDO> carts = cartMapper.selectListByIds(ids, userId);
if (CollUtil.isEmpty(cartItems)) { if (CollUtil.isEmpty(carts)) {
return; return;
} }
// 批量标记删除 // 批量标记删除
cartItemMapper.deleteBatchIds(CollectionUtils.convertSet(cartItems, TradeCartItemDO::getId)); cartMapper.deleteBatchIds(ids);
} }
@Override @Override
public Integer getCartCount(Long userId) { public Integer getCartCount(Long userId) {
return cartItemMapper.selectSumByUserId(userId); return cartMapper.selectSumByUserId(userId);
} }
@Override @Override
public AppTradeCartDetailRespVO getCartDetail(Long userId) { public AppTradeCartListRespVO getCartList(Long userId) {
// 获得购物车的商品 // 获得购物车的商品
List<TradeCartItemDO> cartItems = cartItemMapper.selectListByUserId(userId, null); List<TradeCartDO> carts = cartMapper.selectListByUserId(userId);
carts.sort(Comparator.comparing(TradeCartDO::getId).reversed());
// 如果未空,则返回空结果 // 如果未空,则返回空结果
if (CollUtil.isEmpty(cartItems)) { if (CollUtil.isEmpty(carts)) {
return TradeCartConvert.INSTANCE.buildEmptyAppTradeCartDetailRespVO(); return new AppTradeCartListRespVO().setValidList(emptyList())
.setInvalidList(emptyList());
} }
// 调用价格服务,计算价格 // 查询 SPU、SKU 列表
PriceCalculateRespDTO priceCalculate = priceApi.calculatePrice(TradeCartConvert.INSTANCE.convert(userId, cartItems)); List<ProductSpuRespDTO> spus = productSpuApi.getSpuList(convertSet(carts, TradeCartDO::getSpuId));
List<ProductSkuRespDTO> skus = productSkuApi.getSkuList(convertSet(carts, TradeCartDO::getSpuId));
// 转换返回 // 拼接数据
Map<Long, TradeCartItemDO> cartItemMap = convertMap(cartItems, TradeCartItemDO::getSkuId); return TradeCartConvert.INSTANCE.convertList(carts, spus, skus);
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()));
} }
/** /**