From 3ced6a3240022dcdaadd1c9807495ab3378c5b8f Mon Sep 17 00:00:00 2001
From: YunaiV <zhijiantianya@gmail.com>
Date: Wed, 26 Apr 2023 20:13:12 +0800
Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=20App=20=E5=95=86=E5=93=81?=
 =?UTF-8?q?=E6=90=9C=E7=B4=A2=E7=9A=84=E9=80=BB=E8=BE=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../app/spu/AppProductSpuController.http      | 12 ++++++-
 .../app/spu/AppProductSpuController.java      |  4 +--
 .../spu/vo/AppProductSpuPageItemRespVO.java   | 20 +++++++-----
 .../app/spu/vo/AppProductSpuPageReqVO.java    |  7 +++-
 .../convert/spu/ProductSpuConvert.java        | 13 ++++++--
 .../dal/mysql/spu/ProductSpuMapper.java       | 32 ++++++++++++++-----
 .../service/spu/ProductSpuService.java        |  7 ++--
 .../service/spu/ProductSpuServiceImpl.java    | 24 ++++++++++----
 8 files changed, 87 insertions(+), 32 deletions(-)

diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.http b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.http
index 04df7bfec..0de7583ae 100644
--- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.http
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.http
@@ -1,8 +1,18 @@
-### 获得订单交易的分页 TODO
+### 获得订单交易的分页(默认)
 GET {{appApi}}/product/spu/page?pageNo=1&pageSize=10
 Authorization: Bearer {{appToken}}
 tenant-id: {{appTenentId}}
 
+### 获得订单交易的分页(价格)
+GET {{appApi}}/product/spu/page?pageNo=1&pageSize=10&sortField=price&sortAsc=true
+Authorization: Bearer {{appToken}}
+tenant-id: {{appTenentId}}
+
+### 获得订单交易的分页(销售)
+GET {{appApi}}/product/spu/page?pageNo=1&pageSize=10&sortField=salesCount&sortAsc=true
+Authorization: Bearer {{appToken}}
+tenant-id: {{appTenentId}}
+
 ### 获得商品 SPU 明细
 GET {{appApi}}/product/spu/get-detail?id=4
 tenant-id: {{appTenentId}}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java
index d9ab87c72..783b85780 100644
--- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java
@@ -49,8 +49,8 @@ public class AppProductSpuController {
     @GetMapping("/page")
     @Operation(summary = "获得商品 SPU 分页")
     public CommonResult<PageResult<AppProductSpuPageItemRespVO>> getSpuPage(@Valid AppProductSpuPageReqVO pageVO) {
-        PageResult<ProductSpuDO> pageResult = productSpuService.getSpuPage(pageVO, ProductSpuStatusEnum.ENABLE.getStatus());
-        return success(ProductSpuConvert.INSTANCE.convertPage02(pageResult));
+        PageResult<ProductSpuDO> pageResult = productSpuService.getSpuPage(pageVO);
+        return success(ProductSpuConvert.INSTANCE.convertPageForGetSpuPage(pageResult));
     }
 
     @GetMapping("/get-detail")
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageItemRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageItemRespVO.java
index d826900a6..27d577976 100644
--- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageItemRespVO.java
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageItemRespVO.java
@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.product.controller.app.spu.vo;
 
+import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
@@ -15,21 +16,24 @@ public class AppProductSpuPageItemRespVO {
     private Long id;
 
     @Schema(description = "商品名称", required = true, example = "芋道")
-    @NotEmpty(message = "商品名称不能为空")
     private String name;
 
     @Schema(description = "分类编号", required = true)
-    @NotNull(message = "分类编号不能为空")
     private Long categoryId;
 
-    @Schema(description = "商品图片的数组", required = true)
-    private List<String> picUrls;
+    @Schema(description = "商品封面图", required = true)
+    private String picUrl;
 
-    @Schema(description = " 最小价格,单位使用:分", required = true, example = "1024")
-    private Integer minPrice;
+    @Schema(description = "商品轮播图", required = true)
+    private List<String> sliderPicUrls;
 
-    @Schema(description = "最大价格,单位使用:分", required = true, example = "1024")
-    private Integer maxPrice;
+    @Schema(description = "商品价格,单位使用:分", required = true, example = "1024")
+    private Integer price;
+
+    // ========== SKU 相关字段 =========
+
+    @Schema(description = "库存", required = true, example = "666")
+    private Integer stock;
 
     // ========== 统计相关字段 =========
 
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageReqVO.java
index dcbd1e190..5050561d6 100644
--- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageReqVO.java
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageReqVO.java
@@ -19,18 +19,23 @@ public class AppProductSpuPageReqVO extends PageParam {
     public static final String SORT_FIELD_PRICE = "price";
     public static final String SORT_FIELD_SALES_COUNT = "salesCount";
 
+    public static final String RECOMMEND_TYPE_HOT = "hot";
+
     @Schema(description = "分类编号", example = "1")
     private Long categoryId;
 
     @Schema(description = "关键字", example = "好看")
     private String keyword;
 
-    @Schema(description = "排序字段", example = "price") // 参见 AppSpuPageReqVO.SORT_FIELD_XXX 常量
+    @Schema(description = "排序字段", example = "price") // 参见 AppProductSpuPageReqVO.SORT_FIELD_XXX 常量
     private String sortField;
 
     @Schema(description = "排序方式", example = "true")
     private Boolean sortAsc;
 
+    @Schema(description = "推荐类型", example = "hot") // 参见 AppProductSpuPageReqVO.RECOMMEND_TYPE_XXX 常亮
+    private String recommendType;
+
     @AssertTrue(message = "排序字段不合法")
     @JsonIgnore
     public boolean isSortFieldValid() {
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/spu/ProductSpuConvert.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/spu/ProductSpuConvert.java
index fcf6d6436..cddd742f0 100755
--- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/spu/ProductSpuConvert.java
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/spu/ProductSpuConvert.java
@@ -13,6 +13,7 @@ import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
 import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
 import cn.iocoder.yudao.module.product.service.property.bo.ProductPropertyValueDetailRespBO;
 import org.mapstruct.Mapper;
+import org.mapstruct.Named;
 import org.mapstruct.factory.Mappers;
 
 import java.util.ArrayList;
@@ -75,8 +76,6 @@ public interface ProductSpuConvert {
     List<AppProductSpuDetailRespVO.Sku> convertList03(List<ProductSkuDO> skus);
     AppProductPropertyValueDetailRespVO convert03(ProductPropertyValueDetailRespBO propertyValue);
 
-    PageResult<AppProductSpuPageItemRespVO> convertPage02(PageResult<ProductSpuDO> page);
-
     default ProductSpuDetailRespVO convert03(ProductSpuDO spu, List<ProductSkuDO> skus,
                                              List<ProductPropertyValueDetailRespBO> propertyValues) {
         ProductSpuDetailRespVO spuVO = convert03(spu);
@@ -105,4 +104,14 @@ public interface ProductSpuConvert {
     List<ProductSpuDetailRespVO.Sku> convertList04(List<ProductSkuDO> skus);
     ProductPropertyValueDetailRespVO convert04(ProductPropertyValueDetailRespBO propertyValue);
 
+    // ========== 用户 App 相关 ==========
+
+    default PageResult<AppProductSpuPageItemRespVO> convertPageForGetSpuPage(PageResult<ProductSpuDO> page) {
+        // 累加虚拟销量
+        page.getList().forEach(spu -> spu.setSalesCount(spu.getSalesCount() + spu.getVirtualSalesCount()));
+        // 然后进行转换
+        return convertPageForGetSpuPage0(page);
+    }
+    PageResult<AppProductSpuPageItemRespVO> convertPageForGetSpuPage0(PageResult<ProductSpuDO> page);
+
 }
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/spu/ProductSpuMapper.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/spu/ProductSpuMapper.java
index 256bc43f1..ee0922208 100755
--- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/spu/ProductSpuMapper.java
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/spu/ProductSpuMapper.java
@@ -1,11 +1,15 @@
 package cn.iocoder.yudao.module.product.dal.mysql.spu;
 
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
 import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuPageReqVO;
 import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageReqVO;
 import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
+import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import org.apache.ibatis.annotations.Mapper;
 
@@ -46,16 +50,28 @@ public interface ProductSpuMapper extends BaseMapperX<ProductSpuDO> {
                 .orderByDesc(ProductSpuDO::getSort));
     }
 
-    default PageResult<ProductSpuDO> selectPage(AppProductSpuPageReqVO pageReqVO, Integer status) {
+    /**
+     * 获得商品 SPU 分页,提供给用户 App 使用
+     */
+    default PageResult<ProductSpuDO> selectPage(AppProductSpuPageReqVO pageReqVO, Set<Long> categoryIds) {
         LambdaQueryWrapperX<ProductSpuDO> query = new LambdaQueryWrapperX<ProductSpuDO>()
-                .eqIfPresent(ProductSpuDO::getCategoryId, pageReqVO.getCategoryId())
-                .eqIfPresent(ProductSpuDO::getStatus, status);
+                .likeIfPresent(ProductSpuDO::getName, pageReqVO.getKeyword()) // 关键字匹配,目前只匹配商品名
+                .inIfPresent(ProductSpuDO::getCategoryId, categoryIds); // 分类
+        query.eq(ProductSpuDO::getStatus, ProductSpuStatusEnum.ENABLE.getStatus()) // 上架状态
+                .gt(ProductSpuDO::getStock, 0); // 有库存
+        // 推荐类型的过滤条件
+        if (ObjUtil.equal(pageReqVO.getRecommendType(), AppProductSpuPageReqVO.RECOMMEND_TYPE_HOT)) {
+            query.eq(ProductSpuDO::getRecommendHot, true);
+        }
         // 排序逻辑
-        if (Objects.equals(pageReqVO.getSortField(), AppProductSpuPageReqVO.SORT_FIELD_PRICE)) {
-            // TODO ProductSpuDO 已经没有maxPrice 属性
-            //query.orderBy(true, pageReqVO.getSortAsc(), ProductSpuDO::getMaxPrice);
-        } else if (Objects.equals(pageReqVO.getSortField(), AppProductSpuPageReqVO.SORT_FIELD_SALES_COUNT)) {
-            query.orderBy(true, pageReqVO.getSortAsc(), ProductSpuDO::getSalesCount);
+        if (Objects.equals(pageReqVO.getSortField(), AppProductSpuPageReqVO.SORT_FIELD_SALES_COUNT)) {
+            query.last(String.format(" ORDER BY (sales_count + virtual_sales_count) %s, sort DESC, id DESC",
+                    pageReqVO.getSortAsc() ? "ASC" : "DESC"));
+        } else if (Objects.equals(pageReqVO.getSortField(), AppProductSpuPageReqVO.SORT_FIELD_PRICE)) {
+            query.orderBy(true, pageReqVO.getSortAsc(), ProductSpuDO::getPrice)
+                    .orderByDesc(ProductSpuDO::getSort).orderByDesc(ProductSpuDO::getId);
+        } else {
+            query.orderByDesc(ProductSpuDO::getSort).orderByDesc(ProductSpuDO::getId);
         }
         return selectPage(pageReqVO, query);
     }
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java
index 0ae7359eb..dd8f77c12 100755
--- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java
@@ -75,7 +75,7 @@ public interface ProductSpuService {
     List<ProductSpuDO> getSpuList();
 
     /**
-     * 获得商品 SPU 分页
+     * 获得商品 SPU 分页,提供给挂你兰后台使用
      *
      * @param pageReqVO 分页查询
      * @return 商品spu分页
@@ -83,13 +83,12 @@ public interface ProductSpuService {
     PageResult<ProductSpuDO> getSpuPage(ProductSpuPageReqVO pageReqVO);
 
     /**
-     * 获得商品 SPU 分页
+     * 获得商品 SPU 分页,提供给用户 App 使用
      *
      * @param pageReqVO 分页查询
-     * @param status 状态
      * @return 商品 SPU 分页
      */
-    PageResult<ProductSpuDO> getSpuPage(AppProductSpuPageReqVO pageReqVO, Integer status);
+    PageResult<ProductSpuDO> getSpuPage(AppProductSpuPageReqVO pageReqVO);
 
     /**
      * 更新商品 SPU 库存(增量)
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java
index 352c8b341..79a6949a5 100755
--- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java
@@ -1,14 +1,20 @@
 package cn.iocoder.yudao.module.product.service.spu;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.NumberUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
+import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryListReqVO;
 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO;
 import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuCreateReqVO;
 import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuPageReqVO;
 import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuUpdateReqVO;
 import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageReqVO;
 import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert;
+import cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO;
 import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
 import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
 import cn.iocoder.yudao.module.product.dal.mysql.spu.ProductSpuMapper;
@@ -21,10 +27,7 @@ import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
@@ -168,8 +171,17 @@ public class ProductSpuServiceImpl implements ProductSpuService {
     }
 
     @Override
-    public PageResult<ProductSpuDO> getSpuPage(AppProductSpuPageReqVO pageReqVO, Integer status) {
-        return productSpuMapper.selectPage(pageReqVO, status);
+    public PageResult<ProductSpuDO> getSpuPage(AppProductSpuPageReqVO pageReqVO) {
+        // 查找时,如果查找某个分类编号,则包含它的子分类。因为顶级分类不包含商品
+        Set<Long> categoryIds = new HashSet<>();
+        if (pageReqVO.getCategoryId() != null && pageReqVO.getCategoryId() > 0) {
+            categoryIds.add(pageReqVO.getCategoryId());
+            List<ProductCategoryDO> categoryChildren = categoryService.getEnableCategoryList(new ProductCategoryListReqVO()
+                    .setParentId(pageReqVO.getCategoryId()).setStatus(CommonStatusEnum.ENABLE.getStatus()));
+            categoryIds.addAll(CollectionUtils.convertList(categoryChildren, ProductCategoryDO::getId));
+        }
+        // 分页查询
+        return productSpuMapper.selectPage(pageReqVO, categoryIds);
     }
 
     @Override