Commit da162853ec245d4261a2a6a3c9536dca81ba1b24
1 parent
2113e825
商城:完善购物车的逻辑
Showing
22 changed files
with
351 additions
and
432 deletions
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> { |
| 46 | 46 | return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2)); |
| 47 | 47 | } |
| 48 | 48 | |
| 49 | + default T selectOne(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2, | |
| 50 | + SFunction<T, ?> field3, Object value3) { | |
| 51 | + return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2) | |
| 52 | + .eq(field3, value3)); | |
| 53 | + } | |
| 54 | + | |
| 55 | + default T selectOne(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2, | |
| 56 | + SFunction<T, ?> field3, Object value3, SFunction<T, ?> field4, Object value4) { | |
| 57 | + return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2) | |
| 58 | + .eq(field3, value3).eq(field4, value4)); | |
| 59 | + } | |
| 60 | + | |
| 49 | 61 | default Long selectCount() { |
| 50 | 62 | return selectCount(new QueryWrapper<T>()); |
| 51 | 63 | } | ... | ... |
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 { |
| 28 | 28 | private String spuName; |
| 29 | 29 | |
| 30 | 30 | /** |
| 31 | - * 属性数组,JSON 格式 | |
| 31 | + * 属性数组 | |
| 32 | 32 | */ |
| 33 | 33 | private List<Property> properties; |
| 34 | 34 | /** |
| ... | ... | @@ -85,11 +85,19 @@ public class ProductSkuRespDTO { |
| 85 | 85 | */ |
| 86 | 86 | private Long propertyId; |
| 87 | 87 | /** |
| 88 | + * 属性名字 | |
| 89 | + */ | |
| 90 | + private String propertyName; | |
| 91 | + | |
| 92 | + /** | |
| 88 | 93 | * 属性值编号 |
| 89 | 94 | */ |
| 90 | 95 | private Long valueId; |
| 96 | + /** | |
| 97 | + * 属性值名字 | |
| 98 | + */ | |
| 99 | + private String valueName; | |
| 91 | 100 | |
| 92 | 101 | } |
| 93 | 102 | |
| 94 | - | |
| 95 | 103 | } | ... | ... |
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 { |
| 29 | 29 | */ |
| 30 | 30 | private String name; |
| 31 | 31 | /** |
| 32 | - * 商品编码 | |
| 32 | + * 关键字 | |
| 33 | 33 | */ |
| 34 | - private String code; | |
| 34 | + private String keyword; | |
| 35 | 35 | /** |
| 36 | - * 促销语 | |
| 36 | + * 商品简介 | |
| 37 | 37 | */ |
| 38 | - private String sellPoint; | |
| 38 | + private String introduction; | |
| 39 | 39 | /** |
| 40 | 40 | * 商品详情 |
| 41 | 41 | */ |
| 42 | 42 | private String description; |
| 43 | + // TODO @芋艿:是不是要删除 | |
| 44 | + /** | |
| 45 | + * 商品条码(一维码) | |
| 46 | + */ | |
| 47 | + private String barCode; | |
| 48 | + | |
| 43 | 49 | /** |
| 44 | 50 | * 商品分类编号 |
| 45 | 51 | */ |
| ... | ... | @@ -49,13 +55,13 @@ public class ProductSpuRespDTO { |
| 49 | 55 | */ |
| 50 | 56 | private Long brandId; |
| 51 | 57 | /** |
| 52 | - * 商品图片的数组 | |
| 53 | - * <p> | |
| 54 | - * 1. 第一张图片将作为商品主图,支持同时上传多张图; | |
| 55 | - * 2. 建议使用尺寸 800x800 像素以上、大小不超过 1M 的正方形图片; | |
| 56 | - * 3. 至少 1 张,最多上传 10 张 | |
| 58 | + * 商品封面图 | |
| 57 | 59 | */ |
| 58 | - private List<String> picUrls; | |
| 60 | + private String picUrl; | |
| 61 | + /** | |
| 62 | + * 商品轮播图 | |
| 63 | + */ | |
| 64 | + private List<String> sliderPicUrls; | |
| 59 | 65 | /** |
| 60 | 66 | * 商品视频 |
| 61 | 67 | */ |
| ... | ... | @@ -76,38 +82,27 @@ public class ProductSpuRespDTO { |
| 76 | 82 | |
| 77 | 83 | /** |
| 78 | 84 | * 规格类型 |
| 79 | - * <p> | |
| 80 | - * 枚举 {@link ProductSpuSpecTypeEnum} | |
| 85 | + * | |
| 86 | + * false - 单规格 | |
| 87 | + * true - 多规格 | |
| 81 | 88 | */ |
| 82 | 89 | private Boolean specType; |
| 83 | 90 | /** |
| 84 | - * 最小价格,单位使用:分 | |
| 85 | - * <p> | |
| 86 | - * 基于其对应的 {@link ProductSkuRespDTO#getPrice()} 最小值 | |
| 87 | - */ | |
| 88 | - private Integer minPrice; | |
| 89 | - /** | |
| 90 | - * 最大价格,单位使用:分 | |
| 91 | - * <p> | |
| 92 | - * 基于其对应的 {@link ProductSkuRespDTO#getPrice()} 最大值 | |
| 91 | + * 商品价格,单位使用:分 | |
| 93 | 92 | */ |
| 94 | - private Integer maxPrice; | |
| 93 | + private Integer price; | |
| 95 | 94 | /** |
| 96 | 95 | * 市场价,单位使用:分 |
| 97 | - * <p> | |
| 98 | - * 基于其对应的 {@link ProductSkuRespDTO#getMarketPrice()} 最大值 | |
| 99 | 96 | */ |
| 100 | 97 | private Integer marketPrice; |
| 101 | 98 | /** |
| 102 | - * 总库存 | |
| 103 | - * <p> | |
| 104 | - * 基于其对应的 {@link ProductSkuRespDTO#getStock()} 求和 | |
| 99 | + * 成本价,单位使用:分 | |
| 105 | 100 | */ |
| 106 | - private Integer totalStock; | |
| 101 | + private Integer costPrice; | |
| 107 | 102 | /** |
| 108 | - * 是否展示库存 | |
| 103 | + * 库存 | |
| 109 | 104 | */ |
| 110 | - private Boolean showStock; | |
| 105 | + private Integer stock; | |
| 111 | 106 | |
| 112 | 107 | // ========== 统计相关字段 ========= |
| 113 | 108 | ... | ... |
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; |
| 15 | 15 | import java.util.List; |
| 16 | 16 | |
| 17 | 17 | /** |
| 18 | - * TODO LeeYan9: 类注释; | |
| 18 | + * 商品 SKU API 实现类 | |
| 19 | + * | |
| 19 | 20 | * @author LeeYan9 |
| 20 | 21 | * @since 2022-09-06 |
| 21 | 22 | */ |
| ... | ... | @@ -28,8 +29,8 @@ public class ProductSkuApiImpl implements ProductSkuApi { |
| 28 | 29 | |
| 29 | 30 | @Override |
| 30 | 31 | public ProductSkuRespDTO getSku(Long id) { |
| 31 | - // TODO TODO LeeYan9: 需要实现 | |
| 32 | - return null; | |
| 32 | + ProductSkuDO sku = productSkuService.getSku(id); | |
| 33 | + return ProductSkuConvert.INSTANCE.convert02(sku); | |
| 33 | 34 | } |
| 34 | 35 | |
| 35 | 36 | @Override | ... | ... |
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 { |
| 26 | 26 | |
| 27 | 27 | // ========== SKU 相关字段 ========= |
| 28 | 28 | |
| 29 | + @Schema(description = "规格类型", required = true, example = "true") | |
| 30 | + private Boolean specType; | |
| 31 | + | |
| 29 | 32 | @Schema(description = "商品价格,单位使用:分", required = true, example = "1024") |
| 30 | 33 | private Integer price; |
| 31 | 34 | ... | ... |
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/sku/ProductSkuDO.java
| ... | ... | @@ -110,11 +110,28 @@ public class ProductSkuDO extends BaseDO { |
| 110 | 110 | */ |
| 111 | 111 | private Long propertyId; |
| 112 | 112 | /** |
| 113 | + * 属性名字 | |
| 114 | + * | |
| 115 | + * 冗余 {@link ProductPropertyDO#getName()} | |
| 116 | + * | |
| 117 | + * 注意:每次属性名字发生变化时,需要更新该冗余 | |
| 118 | + */ | |
| 119 | + private String propertyName; | |
| 120 | + | |
| 121 | + /** | |
| 113 | 122 | * 属性值编号 |
| 114 | 123 | * |
| 115 | 124 | * 关联 {@link ProductPropertyValueDO#getId()} |
| 116 | 125 | */ |
| 117 | 126 | private Long valueId; |
| 127 | + /** | |
| 128 | + * 属性值名字 | |
| 129 | + * | |
| 130 | + * 冗余 {@link ProductPropertyValueDO#getName()} | |
| 131 | + * | |
| 132 | + * 注意:每次属性值名字发生变化时,需要更新该冗余 | |
| 133 | + */ | |
| 134 | + private String valueName; | |
| 118 | 135 | |
| 119 | 136 | } |
| 120 | 137 | |
| ... | ... | @@ -139,9 +156,5 @@ public class ProductSkuDO extends BaseDO { |
| 139 | 156 | // TODO 芋艿:pinkStock from y |
| 140 | 157 | // TODO 芋艿:seckillStock from y |
| 141 | 158 | |
| 142 | - // TODO 芋艿:quota from c | |
| 143 | - // TODO 芋艿:quotaShow from c | |
| 144 | - // TODO 芋艿:attrValue from c | |
| 145 | - | |
| 146 | 159 | } |
| 147 | 160 | ... | ... |
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 { |
| 67 | 67 | // 更新 |
| 68 | 68 | ProductPropertyDO updateObj = ProductPropertyConvert.INSTANCE.convert(updateReqVO); |
| 69 | 69 | productPropertyMapper.updateById(updateObj); |
| 70 | + // TODO 芋艿:更新时,需要看看 sku 表 | |
| 70 | 71 | } |
| 71 | 72 | |
| 72 | 73 | @Override | ... | ... |
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 |
| 68 | 68 | // 更新 |
| 69 | 69 | ProductPropertyValueDO updateObj = ProductPropertyValueConvert.INSTANCE.convert(updateReqVO); |
| 70 | 70 | productPropertyValueMapper.updateById(updateObj); |
| 71 | + // TODO 芋艿:更新时,需要看看 sku 表 | |
| 71 | 72 | } |
| 72 | 73 | |
| 73 | 74 | @Override | ... | ... |
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 { |
| 54 | 54 | // mock 数据 |
| 55 | 55 | ProductSkuDO sku01 = randomPojo(ProductSkuDO.class, o -> { // 测试更新 |
| 56 | 56 | o.setSpuId(1L); |
| 57 | - o.setProperties(singletonList(new ProductSkuDO.Property(10L, 20L))); | |
| 57 | + o.setProperties(singletonList(new ProductSkuDO.Property( | |
| 58 | + 10L, "颜色", 20L, "红色"))); | |
| 58 | 59 | }); |
| 59 | 60 | productSkuMapper.insert(sku01); |
| 60 | 61 | ProductSkuDO sku02 = randomPojo(ProductSkuDO.class, o -> { // 测试删除 |
| 61 | 62 | o.setSpuId(1L); |
| 62 | - o.setProperties(singletonList(new ProductSkuDO.Property(10L, 30L))); | |
| 63 | + o.setProperties(singletonList(new ProductSkuDO.Property( | |
| 64 | + 10L, "颜色", 30L, "蓝色"))); | |
| 63 | 65 | }); |
| 64 | 66 | productSkuMapper.insert(sku02); |
| 65 | 67 | // 准备参数 | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/base/spu/AppProductSpuBaseRespVO.java
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/TradeCartController.http
| 1 | -### 请求 /trade/cart/add-count 接口 => 成功 | |
| 2 | -POST {{appApi}}/trade/cart/add-count | |
| 1 | +### 请求 /trade/cart/add 接口 => 成功 | |
| 2 | +POST {{appApi}}/trade/cart/add | |
| 3 | 3 | tenant-id: {{appTenentId}} |
| 4 | 4 | Authorization: Bearer {{appToken}} |
| 5 | 5 | Content-Type: application/json |
| 6 | 6 | |
| 7 | 7 | { |
| 8 | 8 | "skuId": 1, |
| 9 | - "count": 1 | |
| 9 | + "count": 10, | |
| 10 | + "addStatus": true | |
| 10 | 11 | } |
| 11 | 12 | |
| 12 | -### 请求 /trade/cart/update-count 接口 => 成功 | |
| 13 | -PUT {{appApi}}/trade/cart/update-count | |
| 13 | +### 请求 /trade/cart/update 接口 => 成功 | |
| 14 | +PUT {{appApi}}/trade/cart/update | |
| 14 | 15 | tenant-id: {{appTenentId}} |
| 15 | 16 | Authorization: Bearer {{appToken}} |
| 16 | 17 | Content-Type: application/json |
| 17 | 18 | |
| 18 | 19 | { |
| 19 | - "skuId": 1, | |
| 20 | + "id": 35, | |
| 20 | 21 | "count": 5 |
| 21 | 22 | } |
| 22 | 23 | |
| 23 | -### 请求 /trade/cart/update-selected 接口 => 成功 | |
| 24 | -PUT {{appApi}}/trade/cart/update-selected | |
| 25 | -tenant-id: {{appTenentId}} | |
| 26 | -Authorization: Bearer {{appToken}} | |
| 27 | -Content-Type: application/json | |
| 28 | - | |
| 29 | -{ | |
| 30 | - "skuIds": [1], | |
| 31 | - "selected": false | |
| 32 | -} | |
| 33 | - | |
| 34 | 24 | ### 请求 /trade/cart/delete 接口 => 成功 |
| 35 | -DELETE {{appApi}}/trade/cart/delete?skuIds=1 | |
| 25 | +DELETE {{appApi}}/trade/cart/delete?ids=1 | |
| 36 | 26 | tenant-id: {{appTenentId}} |
| 37 | 27 | Authorization: Bearer {{appToken}} |
| 38 | 28 | |
| ... | ... | @@ -41,7 +31,7 @@ GET {{appApi}}/trade/cart/get-count |
| 41 | 31 | tenant-id: {{appTenentId}} |
| 42 | 32 | Authorization: Bearer {{appToken}} |
| 43 | 33 | |
| 44 | -### 请求 /trade/cart/get-detail 接口 => 成功 | |
| 45 | -GET {{appApi}}/trade/cart/get-detail | |
| 34 | +### 请求 /trade/cart/list 接口 => 成功 | |
| 35 | +GET {{appApi}}/trade/cart/list | |
| 46 | 36 | tenant-id: {{appTenentId}} |
| 47 | 37 | Authorization: Bearer {{appToken}} | ... | ... |
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; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | 4 | import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; |
| 5 | -import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartDetailRespVO; | |
| 6 | -import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemAddCountReqVO; | |
| 7 | -import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemUpdateCountReqVO; | |
| 8 | -import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemUpdateSelectedReqVO; | |
| 5 | +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartListRespVO; | |
| 6 | +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartAddReqVO; | |
| 7 | +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartUpdateReqVO; | |
| 9 | 8 | import cn.iocoder.yudao.module.trade.service.cart.TradeCartService; |
| 10 | 9 | import io.swagger.v3.oas.annotations.Operation; |
| 11 | 10 | import io.swagger.v3.oas.annotations.Parameter; |
| ... | ... | @@ -33,37 +32,27 @@ public class TradeCartController { |
| 33 | 32 | @Resource |
| 34 | 33 | private TradeCartService cartService; |
| 35 | 34 | |
| 36 | - @PostMapping("/add-count") | |
| 37 | - @Operation(summary = "添加商品到购物车") | |
| 35 | + @PostMapping("/add") | |
| 36 | + @Operation(summary = "添加购物车商品") | |
| 38 | 37 | @PreAuthenticated |
| 39 | - public CommonResult<Boolean> addCartItemCount(@Valid @RequestBody AppTradeCartItemAddCountReqVO addCountReqVO) { | |
| 40 | - cartService.addCartItemCount(getLoginUserId(), addCountReqVO); | |
| 41 | - return success(true); | |
| 42 | - } | |
| 43 | - | |
| 44 | - @PutMapping("update-count") | |
| 45 | - @Operation(summary = "更新购物车商品数量") | |
| 46 | - @PreAuthenticated | |
| 47 | - public CommonResult<Boolean> updateCartItemQuantity(@Valid @RequestBody AppTradeCartItemUpdateCountReqVO updateCountReqVO) { | |
| 48 | - cartService.updateCartItemCount(getLoginUserId(), updateCountReqVO); | |
| 49 | - return success(true); | |
| 38 | + public CommonResult<Long> addCart(@Valid @RequestBody AppTradeCartAddReqVO addCountReqVO) { | |
| 39 | + return success(cartService.addCart(getLoginUserId(), addCountReqVO)); | |
| 50 | 40 | } |
| 51 | 41 | |
| 52 | - @PutMapping("update-selected") | |
| 53 | - @Operation(summary = "更新购物车商品是否选中") | |
| 42 | + @PutMapping("/update") | |
| 43 | + @Operation(summary = "更新购物车商品") | |
| 54 | 44 | @PreAuthenticated |
| 55 | - public CommonResult<Boolean> updateCartItemSelected(@Valid @RequestBody AppTradeCartItemUpdateSelectedReqVO updateSelectedReqVO) { | |
| 56 | - cartService.updateCartItemSelected(getLoginUserId(), updateSelectedReqVO); | |
| 57 | - // 获得目前购物车明细 | |
| 45 | + public CommonResult<Boolean> updateCartItemQuantity(@Valid @RequestBody AppTradeCartUpdateReqVO updateReqVO) { | |
| 46 | + cartService.updateCart(getLoginUserId(), updateReqVO); | |
| 58 | 47 | return success(true); |
| 59 | 48 | } |
| 60 | 49 | |
| 61 | 50 | @DeleteMapping("/delete") |
| 62 | 51 | @Operation(summary = "删除购物车商品") |
| 63 | - @Parameter(name = "skuIds", description = "商品 SKU 编号的数组", required = true, example = "1024,2048") | |
| 52 | + @Parameter(name = "ids", description = "购物车商品编号", required = true, example = "1024,2048") | |
| 64 | 53 | @PreAuthenticated |
| 65 | - public CommonResult<Boolean> deleteCartItem(@RequestParam("skuIds") List<Long> skuIds) { | |
| 66 | - cartService.deleteCartItems(getLoginUserId(), skuIds); | |
| 54 | + public CommonResult<Boolean> deleteCart(@RequestParam("ids") List<Long> ids) { | |
| 55 | + cartService.deleteCart(getLoginUserId(), ids); | |
| 67 | 56 | return success(true); |
| 68 | 57 | } |
| 69 | 58 | |
| ... | ... | @@ -74,11 +63,11 @@ public class TradeCartController { |
| 74 | 63 | return success(cartService.getCartCount(getLoginUserId())); |
| 75 | 64 | } |
| 76 | 65 | |
| 77 | - @GetMapping("/get-detail") | |
| 78 | - @Operation(summary = "查询用户的购物车的详情") | |
| 66 | + @GetMapping("/list") | |
| 67 | + @Operation(summary = "查询用户的购物车列表") | |
| 79 | 68 | @PreAuthenticated |
| 80 | - public CommonResult<AppTradeCartDetailRespVO> getCartDetail() { | |
| 81 | - return success(cartService.getCartDetail(getLoginUserId())); | |
| 69 | + public CommonResult<AppTradeCartListRespVO> getCartList() { | |
| 70 | + return success(cartService.getCartList(getLoginUserId())); | |
| 82 | 71 | } |
| 83 | 72 | |
| 84 | 73 | } | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/vo/AppTradeCartItemAddCountReqVO.java renamed to 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; |
| 8 | 8 | |
| 9 | 9 | @Schema(description = "用户 App - 购物车添加购物项 Request VO") |
| 10 | 10 | @Data |
| 11 | -public class AppTradeCartItemAddCountReqVO { | |
| 11 | +public class AppTradeCartAddReqVO { | |
| 12 | 12 | |
| 13 | 13 | @Schema(description = "商品 SKU 编号", required = true,example = "1024") |
| 14 | 14 | @NotNull(message = "商品 SKU 编号不能为空") |
| ... | ... | @@ -19,4 +19,8 @@ public class AppTradeCartItemAddCountReqVO { |
| 19 | 19 | @Min(message = "数量必须大于 0", value = 1L) |
| 20 | 20 | private Integer count; |
| 21 | 21 | |
| 22 | + @Schema(description = "是否添加到购物车", required = true, example = "true") | |
| 23 | + @NotNull(message = "是否添加购物车不能为空") | |
| 24 | + private Boolean addStatus; | |
| 25 | + | |
| 22 | 26 | } | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/vo/AppTradeCartDetailRespVO.java deleted
| 1 | -package cn.iocoder.yudao.module.trade.controller.app.cart.vo; | |
| 2 | - | |
| 3 | -import cn.iocoder.yudao.module.trade.controller.app.base.sku.AppProductSkuBaseRespVO; | |
| 4 | -import io.swagger.v3.oas.annotations.media.Schema; | |
| 5 | -import lombok.Data; | |
| 6 | - | |
| 7 | -import java.util.List; | |
| 8 | - | |
| 9 | -@Schema(description = "用户 App - 用户的购物车明细 Response VO") | |
| 10 | -@Data | |
| 11 | -public class AppTradeCartDetailRespVO { | |
| 12 | - | |
| 13 | - /** | |
| 14 | - * 商品分组数组 | |
| 15 | - */ | |
| 16 | - private List<ItemGroup> itemGroups; | |
| 17 | - | |
| 18 | - /** | |
| 19 | - * 费用 | |
| 20 | - */ | |
| 21 | - private Order order; | |
| 22 | - | |
| 23 | - @Schema(description = "商品分组") // 多个商品,参加同一个活动,从而形成分组 | |
| 24 | - @Data | |
| 25 | - public static class ItemGroup { | |
| 26 | - | |
| 27 | - /** | |
| 28 | - * 商品数组 | |
| 29 | - */ | |
| 30 | - private List<Sku> items; | |
| 31 | - /** | |
| 32 | - * 营销活动,订单级别 | |
| 33 | - */ | |
| 34 | - private Promotion promotion; | |
| 35 | - | |
| 36 | - } | |
| 37 | - | |
| 38 | - @Schema(description = "商品 SKU") | |
| 39 | - @Data | |
| 40 | - public static class Sku extends AppProductSkuBaseRespVO { | |
| 41 | - | |
| 42 | - /** | |
| 43 | - * SPU 信息 | |
| 44 | - */ | |
| 45 | - private AppProductSkuBaseRespVO spu; | |
| 46 | - | |
| 47 | - // ========== 购物车相关的字段 ========== | |
| 48 | - | |
| 49 | - @Schema(description = "商品数量", required = true, example = "1") | |
| 50 | - private Integer count; | |
| 51 | - @Schema(description = "是否选中", required = true, example = "true") | |
| 52 | - private Boolean selected; | |
| 53 | - | |
| 54 | - // ========== 价格相关的字段,对应 PriceCalculateRespDTO.OrderItem 的属性 ========== | |
| 55 | - | |
| 56 | - // TODO 芋艿:后续可以去除一些无用的字段 | |
| 57 | - | |
| 58 | - @Schema(description = "商品原价(单)", required = true, example = "100") | |
| 59 | - private Integer originalPrice; | |
| 60 | - @Schema(description = "商品原价(总)", required = true, example = "200") | |
| 61 | - private Integer totalOriginalPrice; | |
| 62 | - @Schema(description = "商品级优惠(总)", required = true, example = "300") | |
| 63 | - private Integer totalPromotionPrice; | |
| 64 | - @Schema(description = "最终购买金额(总)", required = true, example = "400") | |
| 65 | - private Integer totalPresentPrice; | |
| 66 | - @Schema(description = "最终购买金额(单)", required = true, example = "500") | |
| 67 | - private Integer presentPrice; | |
| 68 | - @Schema(description = "应付金额(总)", required = true, example = "600") | |
| 69 | - private Integer totalPayPrice; | |
| 70 | - | |
| 71 | - // ========== 营销相关的字段 ========== | |
| 72 | - /** | |
| 73 | - * 营销活动,商品级别 | |
| 74 | - */ | |
| 75 | - private Promotion promotion; | |
| 76 | - | |
| 77 | - } | |
| 78 | - | |
| 79 | - @Schema(description = "订单") // 对应 PriceCalculateRespDTO.Order 类,用于费用(合计) | |
| 80 | - @Data | |
| 81 | - public static class Order { | |
| 82 | - | |
| 83 | - // TODO 芋艿:后续可以去除一些无用的字段 | |
| 84 | - | |
| 85 | - @Schema(description = "商品原价(总)", required = true, example = "100") | |
| 86 | - private Integer skuOriginalPrice; | |
| 87 | - @Schema(description = "商品优惠(总)", required = true, example = "200") | |
| 88 | - private Integer skuPromotionPrice; | |
| 89 | - @Schema(description = "订单优惠(总)", required = true, example = "300") | |
| 90 | - private Integer orderPromotionPrice; | |
| 91 | - @Schema(description = "运费金额", required = true, example = "400") | |
| 92 | - private Integer deliveryPrice; | |
| 93 | - @Schema(description = "应付金额(总)", required = true, example = "500") | |
| 94 | - private Integer payPrice; | |
| 95 | - | |
| 96 | - } | |
| 97 | - | |
| 98 | - @Schema(description = "营销活动") // 对应 PriceCalculateRespDTO.Promotion 类的属性 | |
| 99 | - @Data | |
| 100 | - public static class Promotion { | |
| 101 | - | |
| 102 | - @Schema(description = "营销编号", required = true, example = "1024") // 营销活动的编号、优惠劵的编号 | |
| 103 | - private Long id; | |
| 104 | - @Schema(description = "营销名字", required = true, example = "xx 活动") | |
| 105 | - private String name; | |
| 106 | - @Schema(description = "营销类型", required = true, example = "1") | |
| 107 | - private Integer type; | |
| 108 | - | |
| 109 | - // ========== 匹配情况 ========== | |
| 110 | - @Schema(description = "是否满足优惠条件", required = true, example = "true") | |
| 111 | - private Boolean meet; | |
| 112 | - @Schema(description = "满足条件的提示", required = true, example = "圣诞价:省 150.00 元") | |
| 113 | - private String meetTip; | |
| 114 | - | |
| 115 | - } | |
| 116 | - | |
| 117 | -} |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/vo/AppTradeCartListRespVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.trade.controller.app.cart.vo; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.module.trade.controller.app.base.sku.AppProductSkuBaseRespVO; | |
| 4 | +import cn.iocoder.yudao.module.trade.controller.app.base.spu.AppProductSpuBaseRespVO; | |
| 5 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 6 | +import lombok.Data; | |
| 7 | + | |
| 8 | +import java.util.List; | |
| 9 | + | |
| 10 | +@Schema(description = "用户 App - 用户的购物列表 Response VO") | |
| 11 | +@Data | |
| 12 | +public class AppTradeCartListRespVO { | |
| 13 | + | |
| 14 | + /** | |
| 15 | + * 有效的购物项数组 | |
| 16 | + */ | |
| 17 | + private List<Cart> validList; | |
| 18 | + | |
| 19 | + /** | |
| 20 | + * 无效的购物项数组 | |
| 21 | + */ | |
| 22 | + private List<Cart> invalidList; | |
| 23 | + | |
| 24 | + @Schema(description = "购物项") | |
| 25 | + @Data | |
| 26 | + public static class Cart { | |
| 27 | + | |
| 28 | + @Schema(description = "购物项的编号", required = true, example = "1024") | |
| 29 | + private Long id; | |
| 30 | + | |
| 31 | + @Schema(description = "商品数量", required = true, example = "1") | |
| 32 | + private Integer count; | |
| 33 | + | |
| 34 | + /** | |
| 35 | + * 商品 SPU | |
| 36 | + */ | |
| 37 | + private AppProductSpuBaseRespVO spu; | |
| 38 | + /** | |
| 39 | + * 商品 SKU | |
| 40 | + */ | |
| 41 | + private AppProductSkuBaseRespVO sku; | |
| 42 | + | |
| 43 | + } | |
| 44 | + | |
| 45 | +} | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/vo/AppTradeCartItemUpdateCountReqVO.java renamed to 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; |
| 6 | 6 | import javax.validation.constraints.Min; |
| 7 | 7 | import javax.validation.constraints.NotNull; |
| 8 | 8 | |
| 9 | -@Schema(description = "用户 App - 购物车更新数量 Request VO") | |
| 9 | +@Schema(description = "用户 App - 购物车更新 Request VO") | |
| 10 | 10 | @Data |
| 11 | -public class AppTradeCartItemUpdateCountReqVO { | |
| 11 | +public class AppTradeCartUpdateReqVO { | |
| 12 | 12 | |
| 13 | - @Schema(description = "商品 SKU 编号", required = true, example = "1024") | |
| 14 | - @NotNull(message = "商品 SKU 编号不能为空") | |
| 15 | - private Long skuId; | |
| 13 | + @Schema(description = "编号", required = true, example = "1024") | |
| 14 | + @NotNull(message = "编号不能为空") | |
| 15 | + private Long id; | |
| 16 | 16 | |
| 17 | 17 | @Schema(description = "商品数量", required = true, example = "1") |
| 18 | 18 | @NotNull(message = "数量不能为空") | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/cart/TradeCartConvert.java
| 1 | 1 | package cn.iocoder.yudao.module.trade.convert.cart; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateReqDTO; | |
| 4 | -import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO; | |
| 5 | -import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartDetailRespVO; | |
| 6 | -import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartItemDO; | |
| 3 | +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; | |
| 4 | +import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; | |
| 5 | +import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; | |
| 6 | +import cn.iocoder.yudao.module.trade.controller.app.base.sku.AppProductSkuBaseRespVO; | |
| 7 | +import cn.iocoder.yudao.module.trade.controller.app.base.spu.AppProductSpuBaseRespVO; | |
| 8 | +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartListRespVO; | |
| 9 | +import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartDO; | |
| 7 | 10 | import org.mapstruct.Mapper; |
| 8 | -import org.mapstruct.Mapping; | |
| 9 | -import org.mapstruct.Mappings; | |
| 10 | 11 | import org.mapstruct.factory.Mappers; |
| 11 | 12 | |
| 12 | -import java.util.Collections; | |
| 13 | +import java.util.ArrayList; | |
| 13 | 14 | import java.util.List; |
| 15 | +import java.util.Map; | |
| 14 | 16 | |
| 15 | -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | |
| 17 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | |
| 16 | 18 | |
| 17 | 19 | @Mapper |
| 18 | 20 | public interface TradeCartConvert { |
| 19 | 21 | |
| 20 | 22 | TradeCartConvert INSTANCE = Mappers.getMapper(TradeCartConvert.class); |
| 21 | 23 | |
| 22 | - default AppTradeCartDetailRespVO buildEmptyAppTradeCartDetailRespVO() { | |
| 23 | - return new AppTradeCartDetailRespVO().setItemGroups(Collections.emptyList()) | |
| 24 | - .setOrder(new AppTradeCartDetailRespVO.Order().setSkuOriginalPrice(0).setSkuPromotionPrice(0) | |
| 25 | - .setOrderPromotionPrice(0).setDeliveryPrice(0).setPayPrice(0)); | |
| 24 | + default AppTradeCartListRespVO convertList(List<TradeCartDO> carts, | |
| 25 | + List<ProductSpuRespDTO> spus, List<ProductSkuRespDTO> skus) { | |
| 26 | + Map<Long, ProductSpuRespDTO> spuMap = convertMap(spus, ProductSpuRespDTO::getId); | |
| 27 | + Map<Long, ProductSkuRespDTO> skuMap = convertMap(skus, ProductSkuRespDTO::getId); | |
| 28 | + // 遍历,开始转换 | |
| 29 | + List<AppTradeCartListRespVO.Cart> validList = new ArrayList<>(carts.size()); | |
| 30 | + List<AppTradeCartListRespVO.Cart> invalidList = new ArrayList<>(); | |
| 31 | + carts.forEach(cart -> { | |
| 32 | + AppTradeCartListRespVO.Cart cartVO = new AppTradeCartListRespVO.Cart(); | |
| 33 | + cartVO.setId(cart.getId()).setCount(cart.getCount()); | |
| 34 | + ProductSpuRespDTO spu = spuMap.get(cart.getSpuId()); | |
| 35 | + ProductSkuRespDTO sku = skuMap.get(cart.getSkuId()); | |
| 36 | + cartVO.setSpu(convert(spu)).setSku(convert(sku)); | |
| 37 | + // 如果 spu 或 sku 不存在,或者 spu 被禁用,说明是非法的,或者 sku 库存不足 | |
| 38 | + if (spu == null | |
| 39 | + || sku == null | |
| 40 | + || !ProductSpuStatusEnum.isEnable(spu.getStatus()) | |
| 41 | + || sku.getStock() <= 0) { | |
| 42 | + invalidList.add(cartVO); | |
| 43 | + } else { | |
| 44 | + validList.add(cartVO); | |
| 45 | + } | |
| 46 | + }); | |
| 47 | + return new AppTradeCartListRespVO().setValidList(validList).setValidList(invalidList); | |
| 26 | 48 | } |
| 27 | - | |
| 28 | - default PriceCalculateReqDTO convert(Long userId, List<TradeCartItemDO> cartItems) { | |
| 29 | - return new PriceCalculateReqDTO().setUserId(userId) | |
| 30 | - .setItems(convertList(cartItems, cartItem -> new PriceCalculateReqDTO.Item().setSkuId(cartItem.getSkuId()) | |
| 31 | - .setCount(cartItem.getSelected() ? cartItem.getCount() : 0))); | |
| 32 | - } | |
| 33 | - | |
| 34 | - // ========== AppTradeCartDetailRespVO 相关 ========== | |
| 35 | - | |
| 36 | - AppTradeCartDetailRespVO.Promotion convert(PriceCalculateRespDTO.Promotion bean); | |
| 37 | - | |
| 38 | - @Mappings({ | |
| 39 | - @Mapping(source = "cartItem.count", target = "count") | |
| 40 | - }) | |
| 41 | - AppTradeCartDetailRespVO.Sku convert(PriceCalculateRespDTO.OrderItem orderItem, TradeCartItemDO cartItem); | |
| 42 | - | |
| 43 | - AppTradeCartDetailRespVO.Order convert(PriceCalculateRespDTO.Order bean); | |
| 49 | + AppProductSpuBaseRespVO convert(ProductSpuRespDTO spu); | |
| 50 | + AppProductSkuBaseRespVO convert(ProductSkuRespDTO sku); | |
| 44 | 51 | |
| 45 | 52 | } | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/cart/TradeCartItemDO.java renamed to 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; |
| 9 | 9 | /** |
| 10 | 10 | * 购物车的商品信息 DO |
| 11 | 11 | * |
| 12 | + * 每个商品,对应一条记录,通过 {@link #spuId} 和 {@link #skuId} 关联 | |
| 13 | + * | |
| 12 | 14 | * @author 芋道源码 |
| 13 | 15 | */ |
| 14 | -@TableName("trade_cart_item") | |
| 16 | +@TableName("trade_cart") | |
| 15 | 17 | @Data |
| 16 | 18 | @EqualsAndHashCode(callSuper = true) |
| 17 | 19 | @Accessors(chain = true) |
| 18 | -public class TradeCartItemDO extends BaseDO { | |
| 20 | +public class TradeCartDO extends BaseDO { | |
| 19 | 21 | |
| 20 | 22 | // ========= 基础字段 BEGIN ========= |
| 21 | 23 | |
| ... | ... | @@ -23,14 +25,6 @@ public class TradeCartItemDO extends BaseDO { |
| 23 | 25 | * 编号,唯一自增 |
| 24 | 26 | */ |
| 25 | 27 | private Long id; |
| 26 | - /** | |
| 27 | - * 是否选中 | |
| 28 | - */ | |
| 29 | - private Boolean selected; | |
| 30 | - | |
| 31 | - // ========= 基础字段 END ========= | |
| 32 | - | |
| 33 | - // ========= 买家信息 BEGIN ========= | |
| 34 | 28 | |
| 35 | 29 | /** |
| 36 | 30 | * 用户编号 |
| ... | ... | @@ -39,7 +33,25 @@ public class TradeCartItemDO extends BaseDO { |
| 39 | 33 | */ |
| 40 | 34 | private Long userId; |
| 41 | 35 | |
| 42 | - // ========= 买家信息 END ========= | |
| 36 | + /** | |
| 37 | + * 是否添加到购物车 | |
| 38 | + * | |
| 39 | + * false - 未添加:用户点击【立即购买】 | |
| 40 | + * true - 已添加:用户点击【添加购物车】 | |
| 41 | + * | |
| 42 | + * 为什么要设计这个字段? | |
| 43 | + * 配合 orderStatus 字段,可以知道有多少商品,用户点击了【立即购买】,最终多少【确认下单】 | |
| 44 | + */ | |
| 45 | + private Boolean addStatus; | |
| 46 | + /** | |
| 47 | + * 是否提交订单 | |
| 48 | + * | |
| 49 | + * false - 未下单:立即购买,或者添加到购物车,此时设置为 false | |
| 50 | + * true - 已下单:确认下单,此时设置为 true | |
| 51 | + */ | |
| 52 | + private Boolean orderStatus; | |
| 53 | + | |
| 54 | + // ========= 基础字段 END ========= | |
| 43 | 55 | |
| 44 | 56 | // ========= 商品信息 BEGIN ========= |
| 45 | 57 | |
| ... | ... | @@ -64,27 +76,11 @@ public class TradeCartItemDO extends BaseDO { |
| 64 | 76 | |
| 65 | 77 | // ========= 优惠信息 BEGIN ========= |
| 66 | 78 | |
| 67 | -// /** | |
| 68 | -// * 商品营销活动编号 | |
| 69 | -// */ | |
| 70 | -// private Long activityId; // discount_id | |
| 71 | -// /** | |
| 72 | -// * 商品营销活动类型 | |
| 73 | -// */ | |
| 74 | -// private Integer activityType; | |
| 75 | 79 | // TODO 芋艿:combination_id 拼团 ID |
| 76 | 80 | // TODO 芋艿:seckill_id 秒杀产品 ID |
| 77 | 81 | // TODO 芋艿:bargain_id 砍价 ID |
| 82 | + // TODO 芋艿:pinkId 团长拼团 ID | |
| 78 | 83 | |
| 79 | 84 | // ========= 优惠信息 END ========= |
| 80 | 85 | |
| 81 | - // TODO 待确定字段:mf | |
| 82 | - // TODO 芋艿:distribution_card_no 推广员 | |
| 83 | - // TODO 芋艿:is_pay 未购买、已购买 | |
| 84 | - // TODO 芋艿:is_new 是否立即购买 | |
| 85 | - | |
| 86 | - // TODO 待确定字段: yv | |
| 87 | - // TODO isPay: 是否购买 | |
| 88 | - // TODO isNew:是否立即购买 | |
| 89 | - | |
| 90 | 86 | } | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/cart/TradeCartItemMapper.java deleted
| 1 | -package cn.iocoder.yudao.module.trade.dal.mysql.cart; | |
| 2 | - | |
| 3 | -import cn.hutool.core.collection.CollUtil; | |
| 4 | -import cn.hutool.core.map.MapUtil; | |
| 5 | -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | |
| 6 | -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | |
| 7 | -import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartItemDO; | |
| 8 | -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |
| 9 | -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | |
| 10 | -import org.apache.ibatis.annotations.Mapper; | |
| 11 | - | |
| 12 | -import java.util.Collection; | |
| 13 | -import java.util.List; | |
| 14 | -import java.util.Map; | |
| 15 | - | |
| 16 | -@Mapper | |
| 17 | -public interface TradeCartItemMapper extends BaseMapperX<TradeCartItemDO> { | |
| 18 | - | |
| 19 | - default TradeCartItemDO selectByUserIdAndSkuId(Long userId, Long skuId) { | |
| 20 | - return selectOne(TradeCartItemDO::getUserId, userId, | |
| 21 | - TradeCartItemDO::getSkuId, skuId); | |
| 22 | - } | |
| 23 | - | |
| 24 | - default List<TradeCartItemDO> selectListByUserIdAndSkuIds(Long userId, Collection<Long> skuIds) { | |
| 25 | - return selectList(new LambdaQueryWrapper<TradeCartItemDO>().eq(TradeCartItemDO::getUserId, userId) | |
| 26 | - .in(TradeCartItemDO::getSkuId, skuIds)); | |
| 27 | - } | |
| 28 | - | |
| 29 | - default void updateByIds(Collection<Long> ids, TradeCartItemDO updateObject) { | |
| 30 | - update(updateObject, new LambdaQueryWrapper<TradeCartItemDO>().in(TradeCartItemDO::getId, ids)); | |
| 31 | - } | |
| 32 | - | |
| 33 | - default Integer selectSumByUserId(Long userId) { | |
| 34 | - // SQL sum 查询 | |
| 35 | - List<Map<String, Object>> result = selectMaps(new QueryWrapper<TradeCartItemDO>() | |
| 36 | - .select("SUM(count) AS sumCount") | |
| 37 | - .eq("user_id", userId)); | |
| 38 | - // 获得数量 | |
| 39 | - return CollUtil.isNotEmpty(result) ? MapUtil.getInt(result.get(0), "sumCount") : 0; | |
| 40 | - } | |
| 41 | - | |
| 42 | - default List<TradeCartItemDO> selectListByUserId(Long userId, Boolean selected) { | |
| 43 | - return selectList(new LambdaQueryWrapperX<TradeCartItemDO>().eq(TradeCartItemDO::getUserId, userId) | |
| 44 | - .eqIfPresent(TradeCartItemDO::getSelected, selected)); | |
| 45 | - } | |
| 46 | - | |
| 47 | -} |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/cart/TradeCartMapper.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.trade.dal.mysql.cart; | |
| 2 | + | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 4 | +import cn.hutool.core.map.MapUtil; | |
| 5 | +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | |
| 6 | +import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartDO; | |
| 7 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |
| 8 | +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | |
| 9 | +import org.apache.ibatis.annotations.Mapper; | |
| 10 | + | |
| 11 | +import java.util.Collection; | |
| 12 | +import java.util.List; | |
| 13 | +import java.util.Map; | |
| 14 | + | |
| 15 | +@Mapper | |
| 16 | +public interface TradeCartMapper extends BaseMapperX<TradeCartDO> { | |
| 17 | + | |
| 18 | + default TradeCartDO selectByUserIdAndSkuId(Long userId, Long skuId, | |
| 19 | + Boolean addStatus, Boolean orderStatus) { | |
| 20 | + return selectOne(TradeCartDO::getUserId, userId, | |
| 21 | + TradeCartDO::getSkuId, skuId, | |
| 22 | + TradeCartDO::getAddStatus, addStatus, | |
| 23 | + TradeCartDO::getOrderStatus, orderStatus); | |
| 24 | + } | |
| 25 | + | |
| 26 | + default Integer selectSumByUserId(Long userId) { | |
| 27 | + // SQL sum 查询 | |
| 28 | + List<Map<String, Object>> result = selectMaps(new QueryWrapper<TradeCartDO>() | |
| 29 | + .select("SUM(count) AS sumCount") | |
| 30 | + .eq("user_id", userId) | |
| 31 | + .eq("add_status", true) // 只计算添加到购物车中的 | |
| 32 | + .eq("order_status", false)); // 必须未下单 | |
| 33 | + // 获得数量 | |
| 34 | + return CollUtil.getFirst(result) != null ? MapUtil.getInt(result.get(0), "sumCount") : 0; | |
| 35 | + } | |
| 36 | + | |
| 37 | + default TradeCartDO selectById(Long id, Long userId) { | |
| 38 | + return selectOne(TradeCartDO::getId, id, | |
| 39 | + TradeCartDO::getUserId, userId); | |
| 40 | + } | |
| 41 | + | |
| 42 | + default List<TradeCartDO> selectListByIds(Collection<Long> ids, Long userId) { | |
| 43 | + return selectList(new LambdaQueryWrapper<TradeCartDO>() | |
| 44 | + .in(TradeCartDO::getId, ids) | |
| 45 | + .eq(TradeCartDO::getUserId, userId)); | |
| 46 | + } | |
| 47 | + | |
| 48 | + default List<TradeCartDO> selectListByUserId(Long userId) { | |
| 49 | + return selectList(TradeCartDO::getUserId, userId); | |
| 50 | + } | |
| 51 | + | |
| 52 | + default void updateByIds(Collection<Long> ids, TradeCartDO updateObject) { | |
| 53 | + update(updateObject, new LambdaQueryWrapper<TradeCartDO>().in(TradeCartDO::getId, ids)); | |
| 54 | + } | |
| 55 | + | |
| 56 | +} | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/cart/TradeCartService.java
| 1 | 1 | package cn.iocoder.yudao.module.trade.service.cart; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartDetailRespVO; | |
| 4 | -import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemAddCountReqVO; | |
| 5 | -import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemUpdateCountReqVO; | |
| 6 | -import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemUpdateSelectedReqVO; | |
| 3 | +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartListRespVO; | |
| 4 | +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartAddReqVO; | |
| 5 | +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartUpdateReqVO; | |
| 7 | 6 | |
| 8 | 7 | import javax.validation.Valid; |
| 9 | 8 | import java.util.Collection; |
| ... | ... | @@ -19,9 +18,10 @@ public interface TradeCartService { |
| 19 | 18 | * 添加商品到购物车 |
| 20 | 19 | * |
| 21 | 20 | * @param userId 用户编号 |
| 22 | - * @param addCountReqVO 添加信息 | |
| 21 | + * @param addReqVO 添加信息 | |
| 22 | + * @return 购物项的编号 | |
| 23 | 23 | */ |
| 24 | - void addCartItemCount(Long userId, @Valid AppTradeCartItemAddCountReqVO addCountReqVO); | |
| 24 | + Long addCart(Long userId, @Valid AppTradeCartAddReqVO addReqVO); | |
| 25 | 25 | |
| 26 | 26 | /** |
| 27 | 27 | * 更新购物车商品数量 |
| ... | ... | @@ -29,23 +29,15 @@ public interface TradeCartService { |
| 29 | 29 | * @param userId 用户编号 |
| 30 | 30 | * @param updateCountReqVO 更新信息 |
| 31 | 31 | */ |
| 32 | - void updateCartItemCount(Long userId, AppTradeCartItemUpdateCountReqVO updateCountReqVO); | |
| 33 | - | |
| 34 | - /** | |
| 35 | - * 更新购物车商品是否选中 | |
| 36 | - * | |
| 37 | - * @param userId 用户编号 | |
| 38 | - * @param updateSelectedReqVO 更新信息 | |
| 39 | - */ | |
| 40 | - void updateCartItemSelected(Long userId, AppTradeCartItemUpdateSelectedReqVO updateSelectedReqVO); | |
| 32 | + void updateCart(Long userId, AppTradeCartUpdateReqVO updateCountReqVO); | |
| 41 | 33 | |
| 42 | 34 | /** |
| 43 | 35 | * 删除购物车商品 |
| 44 | 36 | * |
| 45 | 37 | * @param userId 用户编号 |
| 46 | - * @param skuIds SKU 编号的数组 | |
| 38 | + * @param ids 购物项的编号 | |
| 47 | 39 | */ |
| 48 | - void deleteCartItems(Long userId, Collection<Long> skuIds); | |
| 40 | + void deleteCart(Long userId, Collection<Long> ids); | |
| 49 | 41 | |
| 50 | 42 | /** |
| 51 | 43 | * 查询用户在购物车中的商品数量 |
| ... | ... | @@ -56,11 +48,11 @@ public interface TradeCartService { |
| 56 | 48 | Integer getCartCount(Long userId); |
| 57 | 49 | |
| 58 | 50 | /** |
| 59 | - * 查询用户的购物车详情 | |
| 51 | + * 查询用户的购物车列表 | |
| 60 | 52 | * |
| 61 | 53 | * @param userId 用户编号 |
| 62 | - * @return 购物车详情 | |
| 54 | + * @return 购物车列表 | |
| 63 | 55 | */ |
| 64 | - AppTradeCartDetailRespVO getCartDetail(Long userId); | |
| 56 | + AppTradeCartListRespVO getCartList(Long userId); | |
| 65 | 57 | |
| 66 | 58 | } | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/cart/TradeCartServiceImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.trade.service.cart; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | -import cn.hutool.core.lang.Assert; | |
| 5 | 4 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 6 | -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |
| 7 | -import cn.iocoder.yudao.module.promotion.api.price.PriceApi; | |
| 8 | -import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO; | |
| 9 | -import cn.iocoder.yudao.module.promotion.enums.common.PromotionLevelEnum; | |
| 10 | 5 | import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; |
| 11 | 6 | import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; |
| 12 | -import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartDetailRespVO; | |
| 13 | -import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemAddCountReqVO; | |
| 14 | -import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemUpdateCountReqVO; | |
| 15 | -import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemUpdateSelectedReqVO; | |
| 7 | +import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; | |
| 8 | +import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; | |
| 9 | +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartAddReqVO; | |
| 10 | +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartListRespVO; | |
| 11 | +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartUpdateReqVO; | |
| 16 | 12 | import cn.iocoder.yudao.module.trade.convert.cart.TradeCartConvert; |
| 17 | -import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartItemDO; | |
| 18 | -import cn.iocoder.yudao.module.trade.dal.mysql.cart.TradeCartItemMapper; | |
| 13 | +import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartDO; | |
| 14 | +import cn.iocoder.yudao.module.trade.dal.mysql.cart.TradeCartMapper; | |
| 19 | 15 | import org.springframework.stereotype.Service; |
| 20 | 16 | import org.springframework.validation.annotation.Validated; |
| 21 | 17 | |
| 22 | 18 | import javax.annotation.Resource; |
| 23 | -import java.util.ArrayList; | |
| 24 | 19 | import java.util.Collection; |
| 20 | +import java.util.Comparator; | |
| 25 | 21 | import java.util.List; |
| 26 | -import java.util.Map; | |
| 27 | 22 | |
| 28 | 23 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 29 | -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | |
| 24 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | |
| 30 | 25 | import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS; |
| 31 | 26 | import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_STOCK_NOT_ENOUGH; |
| 32 | 27 | import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.CARD_ITEM_NOT_FOUND; |
| 28 | +import static java.util.Collections.emptyList; | |
| 33 | 29 | |
| 34 | 30 | /** |
| 35 | 31 | * 购物车 Service 实现类 |
| 36 | 32 | * |
| 33 | + * // TODO 芋艿:秒杀、拼团、砍价对购物车的影响 | |
| 34 | + * // TODO 芋艿:未来优化:购物车的价格计算,支持营销信息 | |
| 35 | + * | |
| 37 | 36 | * @author 芋道源码 |
| 38 | 37 | */ |
| 39 | 38 | @Service |
| ... | ... | @@ -41,123 +40,92 @@ import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.CARD_ITEM_N |
| 41 | 40 | public class TradeCartServiceImpl implements TradeCartService { |
| 42 | 41 | |
| 43 | 42 | @Resource |
| 44 | - private TradeCartItemMapper cartItemMapper; | |
| 43 | + private TradeCartMapper cartMapper; | |
| 45 | 44 | |
| 46 | 45 | @Resource |
| 47 | - private ProductSkuApi productSkuApi; | |
| 46 | + private ProductSpuApi productSpuApi; | |
| 48 | 47 | @Resource |
| 49 | - private PriceApi priceApi; | |
| 48 | + private ProductSkuApi productSkuApi; | |
| 50 | 49 | |
| 51 | 50 | @Override |
| 52 | - public void addCartItemCount(Long userId, AppTradeCartItemAddCountReqVO addCountReqVO) { | |
| 53 | - Long skuId = addCountReqVO.getSkuId(); | |
| 54 | - Integer count = addCountReqVO.getCount(); | |
| 55 | - // 查询 CartItemDO | |
| 56 | - TradeCartItemDO tradeItem = cartItemMapper.selectByUserIdAndSkuId(userId, addCountReqVO.getSkuId()); | |
| 57 | - | |
| 58 | - // 存在,则进行数量更新 | |
| 59 | - if (tradeItem != null) { | |
| 60 | - checkProductSku(skuId, tradeItem.getCount() + count); | |
| 61 | - cartItemMapper.updateById(new TradeCartItemDO().setId(tradeItem.getId()) | |
| 62 | - .setSelected(true).setCount(tradeItem.getCount() + count)); | |
| 63 | - return; | |
| 51 | + public Long addCart(Long userId, AppTradeCartAddReqVO addReqVO) { | |
| 52 | + // 查询 TradeCartDO | |
| 53 | + TradeCartDO cart = cartMapper.selectByUserIdAndSkuId(userId, addReqVO.getSkuId(), | |
| 54 | + addReqVO.getAddStatus(), false); | |
| 55 | + // 校验 SKU | |
| 56 | + Integer count = cart != null && addReqVO.getAddStatus() ? | |
| 57 | + cart.getCount() + addReqVO.getCount() : addReqVO.getCount(); | |
| 58 | + ProductSkuRespDTO sku = checkProductSku(addReqVO.getSkuId(), count); | |
| 59 | + | |
| 60 | + // 情况一:存在,则进行数量更新 | |
| 61 | + if (cart != null) { | |
| 62 | + cartMapper.updateById(new TradeCartDO().setId(cart.getId()).setCount(count)); | |
| 63 | + return cart.getId(); | |
| 64 | + // 情况二:不存在,则进行插入 | |
| 65 | + } else { | |
| 66 | + cart = new TradeCartDO().setUserId(userId) | |
| 67 | + .setSpuId(sku.getSpuId()).setSkuId(sku.getId()).setCount(count) | |
| 68 | + .setAddStatus(addReqVO.getAddStatus()).setOrderStatus(false); | |
| 69 | + cartMapper.insert(cart); | |
| 64 | 70 | } |
| 65 | - | |
| 66 | - // 不存在,则进行插入 | |
| 67 | - ProductSkuRespDTO sku = checkProductSku(skuId, count); | |
| 68 | - cartItemMapper.insert(new TradeCartItemDO().setUserId(userId).setSpuId(sku.getSpuId()).setSkuId(sku.getId()) | |
| 69 | - .setSelected(true).setCount(count)); | |
| 71 | + return cart.getId(); | |
| 70 | 72 | } |
| 71 | 73 | |
| 72 | 74 | @Override |
| 73 | - public void updateCartItemCount(Long userId, AppTradeCartItemUpdateCountReqVO updateCountReqVO) { | |
| 74 | - // 校验 TradeCartItemDO 存在 | |
| 75 | - TradeCartItemDO tradeItem = cartItemMapper.selectByUserIdAndSkuId(userId, updateCountReqVO.getSkuId()); | |
| 76 | - if (tradeItem == null) { | |
| 75 | + public void updateCart(Long userId, AppTradeCartUpdateReqVO updateReqVO) { | |
| 76 | + // 校验 TradeCartDO 存在 | |
| 77 | + TradeCartDO cart = cartMapper.selectById(updateReqVO.getId(), userId); | |
| 78 | + if (cart == null) { | |
| 77 | 79 | throw exception(CARD_ITEM_NOT_FOUND); |
| 78 | 80 | } |
| 79 | 81 | // 校验商品 SKU |
| 80 | - checkProductSku(updateCountReqVO.getSkuId(), updateCountReqVO.getCount()); | |
| 82 | + checkProductSku(cart.getSkuId(), updateReqVO.getCount()); | |
| 81 | 83 | |
| 82 | 84 | // 更新数量 |
| 83 | - cartItemMapper.updateById(new TradeCartItemDO().setId(tradeItem.getId()).setCount(updateCountReqVO.getCount())); | |
| 84 | - } | |
| 85 | - | |
| 86 | - @Override | |
| 87 | - public void updateCartItemSelected(Long userId, AppTradeCartItemUpdateSelectedReqVO updateSelectedReqVO) { | |
| 88 | - // 查询 CartItemDO 列表 | |
| 89 | - List<TradeCartItemDO> cartItems = cartItemMapper.selectListByUserIdAndSkuIds(userId, updateSelectedReqVO.getSkuIds()); | |
| 90 | - if (CollUtil.isEmpty(cartItems)) { | |
| 91 | - return; | |
| 92 | - } | |
| 93 | - | |
| 94 | - // 更新选中 | |
| 95 | - cartItemMapper.updateByIds(CollectionUtils.convertList(cartItems, TradeCartItemDO::getId), | |
| 96 | - new TradeCartItemDO().setSelected(updateSelectedReqVO.getSelected())); | |
| 85 | + cartMapper.updateById(new TradeCartDO().setId(cart.getId()) | |
| 86 | + .setCount(updateReqVO.getCount())); | |
| 97 | 87 | } |
| 98 | 88 | |
| 99 | 89 | /** |
| 100 | 90 | * 购物车删除商品 |
| 101 | 91 | * |
| 102 | 92 | * @param userId 用户编号 |
| 103 | - * @param skuIds 商品 SKU 编号的数组 | |
| 93 | + * @param ids 商品 SKU 编号的数组 | |
| 104 | 94 | */ |
| 105 | 95 | @Override |
| 106 | - public void deleteCartItems(Long userId, Collection<Long> skuIds) { | |
| 107 | - // 查询 CartItemDO 列表 | |
| 108 | - List<TradeCartItemDO> cartItems = cartItemMapper.selectListByUserIdAndSkuIds(userId, skuIds); | |
| 109 | - if (CollUtil.isEmpty(cartItems)) { | |
| 96 | + public void deleteCart(Long userId, Collection<Long> ids) { | |
| 97 | + // 查询 TradeCartDO 列表 | |
| 98 | + List<TradeCartDO> carts = cartMapper.selectListByIds(ids, userId); | |
| 99 | + if (CollUtil.isEmpty(carts)) { | |
| 110 | 100 | return; |
| 111 | 101 | } |
| 112 | 102 | |
| 113 | 103 | // 批量标记删除 |
| 114 | - cartItemMapper.deleteBatchIds(CollectionUtils.convertSet(cartItems, TradeCartItemDO::getId)); | |
| 104 | + cartMapper.deleteBatchIds(ids); | |
| 115 | 105 | } |
| 116 | 106 | |
| 117 | 107 | @Override |
| 118 | 108 | public Integer getCartCount(Long userId) { |
| 119 | - return cartItemMapper.selectSumByUserId(userId); | |
| 109 | + return cartMapper.selectSumByUserId(userId); | |
| 120 | 110 | } |
| 121 | 111 | |
| 122 | 112 | @Override |
| 123 | - public AppTradeCartDetailRespVO getCartDetail(Long userId) { | |
| 113 | + public AppTradeCartListRespVO getCartList(Long userId) { | |
| 124 | 114 | // 获得购物车的商品 |
| 125 | - List<TradeCartItemDO> cartItems = cartItemMapper.selectListByUserId(userId, null); | |
| 115 | + List<TradeCartDO> carts = cartMapper.selectListByUserId(userId); | |
| 116 | + carts.sort(Comparator.comparing(TradeCartDO::getId).reversed()); | |
| 126 | 117 | // 如果未空,则返回空结果 |
| 127 | - if (CollUtil.isEmpty(cartItems)) { | |
| 128 | - return TradeCartConvert.INSTANCE.buildEmptyAppTradeCartDetailRespVO(); | |
| 118 | + if (CollUtil.isEmpty(carts)) { | |
| 119 | + return new AppTradeCartListRespVO().setValidList(emptyList()) | |
| 120 | + .setInvalidList(emptyList()); | |
| 129 | 121 | } |
| 130 | 122 | |
| 131 | - // 调用价格服务,计算价格 | |
| 132 | - PriceCalculateRespDTO priceCalculate = priceApi.calculatePrice(TradeCartConvert.INSTANCE.convert(userId, cartItems)); | |
| 133 | - | |
| 134 | - // 转换返回 | |
| 135 | - Map<Long, TradeCartItemDO> cartItemMap = convertMap(cartItems, TradeCartItemDO::getSkuId); | |
| 136 | - Map<Long, PriceCalculateRespDTO.OrderItem> orderItemMap = convertMap(priceCalculate.getOrder().getItems(), | |
| 137 | - PriceCalculateRespDTO.OrderItem::getSkuId); | |
| 138 | - List<AppTradeCartDetailRespVO.ItemGroup> itemGroups = new ArrayList<>(cartItems.size()); | |
| 139 | - // ① 场景一,营销活动,订单级别 TODO 芋艿:待测试 | |
| 140 | - priceCalculate.getPromotions().stream().filter(promotion -> PromotionLevelEnum.ORDER.getLevel().equals(promotion.getLevel())) | |
| 141 | - .forEach(promotion -> { | |
| 142 | - AppTradeCartDetailRespVO.ItemGroup itemGroup = new AppTradeCartDetailRespVO.ItemGroup().setItems(new ArrayList<>()) | |
| 143 | - .setPromotion(TradeCartConvert.INSTANCE.convert(promotion)); | |
| 144 | - itemGroups.add(itemGroup); | |
| 145 | - promotion.getItems().forEach(promotionItem -> { | |
| 146 | - PriceCalculateRespDTO.OrderItem orderItem = orderItemMap.remove(promotionItem.getSkuId()); | |
| 147 | - Assert.notNull(orderItem, "商品 SKU({}) 对应的订单项不能为空", promotionItem.getSkuId()); | |
| 148 | - TradeCartItemDO cartItem = cartItemMap.get(orderItem.getSkuId()); | |
| 149 | - itemGroup.getItems().add(TradeCartConvert.INSTANCE.convert(orderItem, cartItem)); // TODO spu | |
| 150 | - }); | |
| 151 | - }); | |
| 152 | - // ② 场景二,营销活动,商品级别 | |
| 153 | - orderItemMap.values().forEach(orderItem -> { | |
| 154 | - AppTradeCartDetailRespVO.ItemGroup itemGroup = new AppTradeCartDetailRespVO.ItemGroup().setItems(new ArrayList<>(1)).setPromotion(null); | |
| 155 | - itemGroups.add(itemGroup); | |
| 156 | - TradeCartItemDO cartItem = cartItemMap.get(orderItem.getSkuId()); | |
| 157 | - itemGroup.getItems().add(TradeCartConvert.INSTANCE.convert(orderItem, cartItem)); // TODO spu | |
| 158 | - }); | |
| 159 | - return new AppTradeCartDetailRespVO().setItemGroups(itemGroups) | |
| 160 | - .setOrder(TradeCartConvert.INSTANCE.convert(priceCalculate.getOrder())); | |
| 123 | + // 查询 SPU、SKU 列表 | |
| 124 | + List<ProductSpuRespDTO> spus = productSpuApi.getSpuList(convertSet(carts, TradeCartDO::getSpuId)); | |
| 125 | + List<ProductSkuRespDTO> skus = productSkuApi.getSkuList(convertSet(carts, TradeCartDO::getSpuId)); | |
| 126 | + | |
| 127 | + // 拼接数据 | |
| 128 | + return TradeCartConvert.INSTANCE.convertList(carts, spus, skus); | |
| 161 | 129 | } |
| 162 | 130 | |
| 163 | 131 | /** | ... | ... |