소스 검색

商城:完善购物车的逻辑

YunaiV 2 년 전
부모
커밋
da162853ec
22개의 변경된 파일351개의 추가작업 그리고 432개의 파일을 삭제
  1. 12 0
      yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
  2. 10 2
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuRespDTO.java
  3. 25 30
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/dto/ProductSpuRespDTO.java
  4. 4 3
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApiImpl.java
  5. 3 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageItemRespVO.java
  6. 17 4
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/sku/ProductSkuDO.java
  7. 1 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/property/ProductPropertyServiceImpl.java
  8. 1 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/property/ProductPropertyValueServiceImpl.java
  9. 4 2
      yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceTest.java
  10. 1 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/base/spu/AppProductSpuBaseRespVO.java
  11. 10 20
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/TradeCartController.http
  12. 18 29
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/TradeCartController.java
  13. 5 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/vo/AppTradeCartAddReqVO.java
  14. 0 117
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/vo/AppTradeCartDetailRespVO.java
  15. 45 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/vo/AppTradeCartListRespVO.java
  16. 5 5
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/vo/AppTradeCartUpdateReqVO.java
  17. 36 29
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/cart/TradeCartConvert.java
  18. 24 28
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/cart/TradeCartDO.java
  19. 0 47
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/cart/TradeCartItemMapper.java
  20. 56 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/cart/TradeCartMapper.java
  21. 12 20
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/cart/TradeCartService.java
  22. 62 94
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/cart/TradeCartServiceImpl.java

+ 12 - 0
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java

@@ -46,6 +46,18 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
         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() {
         return selectCount(new QueryWrapper<T>());
     }

+ 10 - 2
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuRespDTO.java

@@ -28,7 +28,7 @@ public class ProductSkuRespDTO {
     private String spuName;
 
     /**
-     * 属性数组,JSON 格式
+     * 属性数组
      */
     private List<Property> properties;
     /**
@@ -84,12 +84,20 @@ public class ProductSkuRespDTO {
          * 属性编号
          */
         private Long propertyId;
+        /**
+         * 属性名字
+         */
+        private String propertyName;
+
         /**
          * 属性值编号
          */
         private Long valueId;
+        /**
+         * 属性值名字
+         */
+        private String valueName;
 
     }
 
-
 }

+ 25 - 30
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/dto/ProductSpuRespDTO.java

@@ -29,17 +29,23 @@ public class ProductSpuRespDTO {
      */
     private String name;
     /**
-     * 商品编码
+     * 关键字
      */
-    private String code;
+    private String keyword;
     /**
-     * 促销语
+     * 商品简介
      */
-    private String sellPoint;
+    private String introduction;
     /**
      * 商品详情
      */
     private String description;
+    // TODO @芋艿:是不是要删除
+    /**
+     * 商品条码(一维码)
+     */
+    private String barCode;
+
     /**
      * 商品分类编号
      */
@@ -49,13 +55,13 @@ public class ProductSpuRespDTO {
      */
     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;
     /**
-     * 最小价格,单位使用:分
-     * <p>
-     * 基于其对应的 {@link ProductSkuRespDTO#getPrice()} 最小值
-     */
-    private Integer minPrice;
-    /**
-     * 最大价格,单位使用:分
-     * <p>
-     * 基于其对应的 {@link ProductSkuRespDTO#getPrice()} 最大值
+     * 商品价格,单位使用:分
      */
-    private Integer maxPrice;
+    private Integer price;
     /**
      * 市场价,单位使用:分
-     * <p>
-     * 基于其对应的 {@link ProductSkuRespDTO#getMarketPrice()} 最大值
      */
     private Integer marketPrice;
     /**
-     * 总库存
-     * <p>
-     * 基于其对应的 {@link ProductSkuRespDTO#getStock()} 求和
+     * 成本价,单位使用:分
      */
-    private Integer totalStock;
+    private Integer costPrice;
     /**
-     * 是否展示库存
+     * 库存
      */
-    private Boolean showStock;
+    private Integer stock;
 
     // ========== 统计相关字段 =========
 

+ 4 - 3
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApiImpl.java

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

+ 3 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageItemRespVO.java

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

+ 17 - 4
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/sku/ProductSkuDO.java

@@ -109,12 +109,29 @@ public class ProductSkuDO extends BaseDO {
          * 关联 {@link ProductPropertyDO#getId()}
          */
         private Long propertyId;
+        /**
+         * 属性名字
+         *
+         * 冗余 {@link ProductPropertyDO#getName()}
+         *
+         * 注意:每次属性名字发生变化时,需要更新该冗余
+         */
+        private String propertyName;
+
         /**
          * 属性值编号
          *
          * 关联 {@link ProductPropertyValueDO#getId()}
          */
         private Long valueId;
+        /**
+         * 属性值名字
+         *
+         * 冗余 {@link ProductPropertyValueDO#getName()}
+         *
+         * 注意:每次属性值名字发生变化时,需要更新该冗余
+         */
+        private String valueName;
 
     }
 
@@ -139,9 +156,5 @@ public class ProductSkuDO extends BaseDO {
     // TODO 芋艿:pinkStock from y
     // TODO 芋艿:seckillStock from y
 
-    // TODO 芋艿:quota from c
-    // TODO 芋艿:quotaShow from c
-    // TODO 芋艿:attrValue from c
-
 }
 

+ 1 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/property/ProductPropertyServiceImpl.java

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

+ 1 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/property/ProductPropertyValueServiceImpl.java

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

+ 4 - 2
yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceTest.java

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

+ 1 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/base/spu/AppProductSpuBaseRespVO.java

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

+ 10 - 20
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/TradeCartController.http

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

+ 18 - 29
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/TradeCartController.java

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

+ 5 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/vo/AppTradeCartItemAddCountReqVO.java → yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/vo/AppTradeCartAddReqVO.java

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

+ 0 - 117
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/vo/AppTradeCartDetailRespVO.java

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

+ 45 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/vo/AppTradeCartListRespVO.java

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

+ 5 - 5
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/vo/AppTradeCartItemUpdateCountReqVO.java → yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/vo/AppTradeCartUpdateReqVO.java

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

+ 36 - 29
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/cart/TradeCartConvert.java

@@ -1,45 +1,52 @@
 package cn.iocoder.yudao.module.trade.convert.cart;
 
-import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateReqDTO;
-import cn.iocoder.yudao.module.promotion.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 cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
+import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
+import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
+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.Mapping;
-import org.mapstruct.Mappings;
 import org.mapstruct.factory.Mappers;
 
-import java.util.Collections;
+import java.util.ArrayList;
 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
 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 AppTradeCartListRespVO convertList(List<TradeCartDO> carts,
+                                               List<ProductSpuRespDTO> spus, List<ProductSkuRespDTO> skus) {
+        Map<Long, ProductSpuRespDTO> spuMap = convertMap(spus, ProductSpuRespDTO::getId);
+        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);
+            }
+        });
+        return new AppTradeCartListRespVO().setValidList(validList).setValidList(invalidList);
     }
-
-    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);
+    AppProductSpuBaseRespVO convert(ProductSpuRespDTO spu);
+    AppProductSkuBaseRespVO convert(ProductSkuRespDTO sku);
 
 }

+ 24 - 28
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/cart/TradeCartItemDO.java → yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/cart/TradeCartDO.java

@@ -9,13 +9,15 @@ import lombok.experimental.Accessors;
 /**
  * 购物车的商品信息 DO
  *
+ * 每个商品,对应一条记录,通过 {@link #spuId} 和 {@link #skuId} 关联
+ *
  * @author 芋道源码
  */
-@TableName("trade_cart_item")
+@TableName("trade_cart")
 @Data
 @EqualsAndHashCode(callSuper = true)
 @Accessors(chain = true)
-public class TradeCartItemDO extends BaseDO {
+public class TradeCartDO extends BaseDO {
 
     // ========= 基础字段 BEGIN =========
 
@@ -23,14 +25,6 @@ public class TradeCartItemDO extends BaseDO {
      * 编号,唯一自增
      */
     private Long id;
-    /**
-     * 是否选中
-     */
-    private Boolean selected;
-
-    // ========= 基础字段 END =========
-
-    // ========= 买家信息 BEGIN =========
 
     /**
      * 用户编号
@@ -39,7 +33,25 @@ public class TradeCartItemDO extends BaseDO {
      */
     private Long userId;
 
-    // ========= 买家信息 END =========
+    /**
+     * 是否添加到购物车
+     *
+     * false - 未添加:用户点击【立即购买】
+     * true - 已添加:用户点击【添加购物车】
+     *
+     * 为什么要设计这个字段?
+     *      配合 orderStatus 字段,可以知道有多少商品,用户点击了【立即购买】,最终多少【确认下单】
+     */
+    private Boolean addStatus;
+    /**
+     * 是否提交订单
+     *
+     * false - 未下单:立即购买,或者添加到购物车,此时设置为 false
+     * true - 已下单:确认下单,此时设置为 true
+     */
+    private Boolean orderStatus;
+
+    // ========= 基础字段 END =========
 
     // ========= 商品信息 BEGIN =========
 
@@ -64,27 +76,11 @@ public class TradeCartItemDO extends BaseDO {
 
     // ========= 优惠信息 BEGIN =========
 
-//    /**
-//     * 商品营销活动编号
-//     */
-//    private Long activityId; // discount_id
-//    /**
-//     * 商品营销活动类型
-//     */
-//    private Integer activityType;
     // TODO 芋艿:combination_id 拼团 ID
     // TODO 芋艿:seckill_id 秒杀产品 ID
     // TODO 芋艿:bargain_id 砍价 ID
+    // TODO 芋艿:pinkId 团长拼团 ID
 
     // ========= 优惠信息 END =========
 
-    // TODO 待确定字段:mf
-    // TODO 芋艿:distribution_card_no 推广员
-    // TODO 芋艿:is_pay 未购买、已购买
-    // TODO 芋艿:is_new 是否立即购买
-
-    // TODO 待确定字段: yv
-    // TODO isPay: 是否购买
-    // TODO isNew:是否立即购买
-
 }

+ 0 - 47
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/cart/TradeCartItemMapper.java

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

+ 56 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/cart/TradeCartMapper.java

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

+ 12 - 20
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/cart/TradeCartService.java

@@ -1,9 +1,8 @@
 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 cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartListRespVO;
+import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartAddReqVO;
+import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartUpdateReqVO;
 
 import javax.validation.Valid;
 import java.util.Collection;
@@ -19,9 +18,10 @@ public interface TradeCartService {
      * 添加商品到购物车
      *
      * @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 updateCountReqVO 更新信息
      */
-    void updateCartItemCount(Long userId, AppTradeCartItemUpdateCountReqVO updateCountReqVO);
-
-    /**
-     * 更新购物车商品是否选中
-     *
-     * @param userId 用户编号
-     * @param updateSelectedReqVO 更新信息
-     */
-    void updateCartItemSelected(Long userId, AppTradeCartItemUpdateSelectedReqVO updateSelectedReqVO);
+    void updateCart(Long userId, AppTradeCartUpdateReqVO updateCountReqVO);
 
     /**
      * 删除购物车商品
      *
      * @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);
 
     /**
-     * 查询用户的购物车详情
+     * 查询用户的购物车列表
      *
      * @param userId 用户编号
-     * @return 购物车详情
+     * @return 购物车列表
      */
-    AppTradeCartDetailRespVO getCartDetail(Long userId);
+    AppTradeCartListRespVO getCartList(Long userId);
 
 }

+ 62 - 94
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/cart/TradeCartServiceImpl.java

@@ -1,39 +1,38 @@
 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.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.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.product.api.spu.ProductSpuApi;
+import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
+import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartAddReqVO;
+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.dal.dataobject.cart.TradeCartItemDO;
-import cn.iocoder.yudao.module.trade.dal.mysql.cart.TradeCartItemMapper;
+import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartDO;
+import cn.iocoder.yudao.module.trade.dal.mysql.cart.TradeCartMapper;
 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.Comparator;
 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.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_STOCK_NOT_ENOUGH;
 import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.CARD_ITEM_NOT_FOUND;
+import static java.util.Collections.emptyList;
 
 /**
  * 购物车 Service 实现类
  *
+ * // TODO 芋艿:秒杀、拼团、砍价对购物车的影响
+ * // TODO 芋艿:未来优化:购物车的价格计算,支持营销信息
+ *
  * @author 芋道源码
  */
 @Service
@@ -41,123 +40,92 @@ import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.CARD_ITEM_N
 public class TradeCartServiceImpl implements TradeCartService {
 
     @Resource
-    private TradeCartItemMapper cartItemMapper;
+    private TradeCartMapper cartMapper;
 
     @Resource
-    private ProductSkuApi productSkuApi;
+    private ProductSpuApi productSpuApi;
     @Resource
-    private PriceApi priceApi;
+    private ProductSkuApi productSkuApi;
 
     @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;
+    public Long addCart(Long userId, AppTradeCartAddReqVO addReqVO) {
+        // 查询 TradeCartDO
+        TradeCartDO cart = cartMapper.selectByUserIdAndSkuId(userId, addReqVO.getSkuId(),
+                addReqVO.getAddStatus(), false);
+        // 校验 SKU
+        Integer count = cart != null && addReqVO.getAddStatus() ?
+                cart.getCount() + addReqVO.getCount() : addReqVO.getCount();
+        ProductSkuRespDTO sku = checkProductSku(addReqVO.getSkuId(), count);
+
+        // 情况一:存在,则进行数量更新
+        if (cart != null) {
+            cartMapper.updateById(new TradeCartDO().setId(cart.getId()).setCount(count));
+            return cart.getId();
+        // 情况二:不存在,则进行插入
+        } else {
+            cart = new TradeCartDO().setUserId(userId)
+                    .setSpuId(sku.getSpuId()).setSkuId(sku.getId()).setCount(count)
+                    .setAddStatus(addReqVO.getAddStatus()).setOrderStatus(false);
+            cartMapper.insert(cart);
         }
-
-        // 不存在,则进行插入
-        ProductSkuRespDTO sku = checkProductSku(skuId, count);
-        cartItemMapper.insert(new TradeCartItemDO().setUserId(userId).setSpuId(sku.getSpuId()).setSkuId(sku.getId())
-                .setSelected(true).setCount(count));
+        return cart.getId();
     }
 
     @Override
-    public void updateCartItemCount(Long userId, AppTradeCartItemUpdateCountReqVO updateCountReqVO) {
-        // 校验 TradeCartItemDO 存在
-        TradeCartItemDO tradeItem = cartItemMapper.selectByUserIdAndSkuId(userId, updateCountReqVO.getSkuId());
-        if (tradeItem == null) {
+    public void updateCart(Long userId, AppTradeCartUpdateReqVO updateReqVO) {
+        // 校验 TradeCartDO 存在
+        TradeCartDO cart = cartMapper.selectById(updateReqVO.getId(), userId);
+        if (cart == null) {
             throw exception(CARD_ITEM_NOT_FOUND);
         }
         // 校验商品 SKU
-        checkProductSku(updateCountReqVO.getSkuId(), updateCountReqVO.getCount());
+        checkProductSku(cart.getSkuId(), updateReqVO.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()));
+        cartMapper.updateById(new TradeCartDO().setId(cart.getId())
+                .setCount(updateReqVO.getCount()));
     }
 
     /**
      * 购物车删除商品
      *
      * @param userId 用户编号
-     * @param skuIds 商品 SKU 编号的数组
+     * @param ids 商品 SKU 编号的数组
      */
     @Override
-    public void deleteCartItems(Long userId, Collection<Long> skuIds) {
-        // 查询 CartItemDO 列表
-        List<TradeCartItemDO> cartItems = cartItemMapper.selectListByUserIdAndSkuIds(userId, skuIds);
-        if (CollUtil.isEmpty(cartItems)) {
+    public void deleteCart(Long userId, Collection<Long> ids) {
+        // 查询 TradeCartDO 列表
+        List<TradeCartDO> carts = cartMapper.selectListByIds(ids, userId);
+        if (CollUtil.isEmpty(carts)) {
             return;
         }
 
         // 批量标记删除
-        cartItemMapper.deleteBatchIds(CollectionUtils.convertSet(cartItems, TradeCartItemDO::getId));
+        cartMapper.deleteBatchIds(ids);
     }
 
     @Override
     public Integer getCartCount(Long userId) {
-        return cartItemMapper.selectSumByUserId(userId);
+        return cartMapper.selectSumByUserId(userId);
     }
 
     @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)) {
-            return TradeCartConvert.INSTANCE.buildEmptyAppTradeCartDetailRespVO();
+        if (CollUtil.isEmpty(carts)) {
+            return new AppTradeCartListRespVO().setValidList(emptyList())
+                    .setInvalidList(emptyList());
         }
 
-        // 调用价格服务,计算价格
-        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()));
+        // 查询 SPU、SKU 列表
+        List<ProductSpuRespDTO> spus = productSpuApi.getSpuList(convertSet(carts, TradeCartDO::getSpuId));
+        List<ProductSkuRespDTO> skus = productSkuApi.getSkuList(convertSet(carts, TradeCartDO::getSpuId));
+
+        // 拼接数据
+        return TradeCartConvert.INSTANCE.convertList(carts, spus, skus);
     }
 
     /**