尚硅谷_尚品甄选_第3章_移动端数据展示.md 23 KB

[TOC]

第3章 移动端数据展示

项目演示地址:http://ry-spzx.atguigu.cn/

1 前端部署

前端H5部分我们不需要开发,只需要根据接口文档开发微服务接口,然后对接到写好的前端H5即可

1.1 启动nginx

在资料中找到nginx压缩包,解压到非中文目录下,修改配置文件conf/nginx.conf

listen       81;

打开命令行,进入到nginx目录,执行以下命令运行nginx:

#启动
nginx.exe
#停止
nginx.exe -s stop
#重新加载配置
nginx.exe -s reload

1.2 部署h5程序

在资料中找到app前端打包后的代码.zip,解压到nginx的html目录下:

image-20241111031950403

浏览器访问http://localhost:81

1.3 接口设置

访问移动端前端http://localhost:81/#/pages/set/set,选择右上角`配置`按钮

image-20241115154314926

跳转到设置页面,点击“接口base路径”,修改baseUrl为本地的微服务网关地址:http://localhost:8080

注意:如果删除浏览器历史记录,这个baseUrl需要重新配置

image-20241115154116976

1.4 配置白名单

访问首页测试,发现跳转到了登录页面。

原因:接口的远程访问被网关服务拦截了,AuthFilter中校验了令牌

移动端首页、列表页、详情页无需登录即可访问,因此可以将这些页面放入网关白名单

在nacos的spzx-gateway-dev.yml中配置网关白名单:

# 不校验白名单
ignore:
  whites:
    ...
    - /product/channel/**

白名单配置的加载在网关模块spzx-gateway的IgnoreWhiteProperties中,设置了注解@RefreshScope,因此可以热更新

2 首页接口开发

2.1 需求分析

首页展示商品一级分类数据以及畅销商品列表数据,如下所示:

image-20230703225931377

2.2 接口文档

首页接口地址及示例数据

get  /product/channel/index
返回结果:
{
  "msg": "操作成功",
  "code": 200,
  "data": {
    "productSkuList": [
      {
        "id": 5,
        "skuName": "小米 红米Note10 5G手机 黑色 + 8G",
        "thumbImg": "http://139.198.127.41:9000/spzx/20230525/665832167-1_u_1.jpg",
        "salePrice": 1999,
        "saleNum": 2
      },
      ...
    ],
    "categoryList": [
      {
        "id": 1,
        "name": "数码办公",
        "imageUrl": "https://lilishop-oss.oss-cn-beijing.aliyuncs.com/230f48f024a343c6be9be72597c2dcd0.png",
        "parentId": 0
      },
      ...
    ]
  }
}

2.3 获取商品一级分类

查询category表,获取parent_id="0"的数据列表

2.3.1 创建CategoryVo

package com.spzx.product.vo;

@Schema(description = "商品分类VO")
@Data
public class CategoryVo {

    @Schema(description = "分类id")
    private Long id;

    @Schema(description = "分类名称")
    private String name;

    @Schema(description = "图标地址")
    private String imageUrl;

    @Schema(description = "上级分类id")
    private Long parentId;
}

2.3.2 ICategoryService

List<CategoryVo> getLevelOneCategory();

2.3.3 CategoryServiceImpl

@Override
public List<CategoryVo> getLevelOneCategory() {
    //查询所有一级分类
    List<Category> allCategoryList = baseMapper.selectList(
        new LambdaQueryWrapper<Category>().eq(Category::getParentId, 0)
    );
    //转换为vo
    return allCategoryList.stream().map(item -> {
        CategoryVo categoryVo = new CategoryVo();
        BeanUtils.copyProperties(item, categoryVo);
        return categoryVo;
    }).collect(Collectors.toList());
}

2.4 获取畅销商品列表

查询productSku表和stock库存表,根据sku_stock表sale_num字段排序,取前20条数据列表,我们通过自定义sql来实现

2.4.1 创建ProductSkuVo

package com.spzx.product.vo;

@Schema(description = "商品skuVo")
@Data
public class ProductSkuVo {

    @Schema(description = "商品skuid")
    private Long id;

    @Schema(description = "商品sku名称")
    private String skuName;

    @Schema(description = "商品sku图片")
    private String thumbImg;

    @Schema(description = "商品sku价格")
    private BigDecimal salePrice;

    @Schema(description = "商品sku销量")
    private Integer saleNum;
}

2.4.2 IProductSkuService

List<ProductSkuVo> getTopSale();

2.4.3 ProductSkuServiceImpl

@Override
public List<ProductSkuVo> getTopSale() {
    return baseMapper.selectTopSale();
}

2.4.4 ProductSkuMapper

List<ProductSkuVo> selectTopSale();

2.4.5 ProductSkuMapper.xml

<select id="selectTopSale" resultType="com.spzx.product.vo.ProductSkuVo">
        SELECT sku.id,
               sku.sku_name,
               sku.thumb_img,
               sku.sale_price,
               ss.sale_num
        FROM product_sku sku
                 LEFT JOIN sku_stock ss ON ss.sku_id = sku.id AND ss.del_flag = 0
        WHERE sku.status = 1
          AND sku.del_flag = 0
        ORDER BY ss.sale_num DESC LIMIT 20
</select>

2.5 接口开发

2.5.1 创建IndexDataVo

封装首页数据

package com.spzx.product.vo;

@Schema(description = "首页数据")
@Data
public class IndexDataVo {

    @Schema(description = "商品分类VO列表")
    private List<CategoryVo> categoryList;
    
    @Schema(description = "商品skuVO列表")
    private List<ProductSkuVo> productSkuList;
}

2.5.2 创建ChannelController

package com.spzx.product.controller;

@Tag(name = "聚合数据")
@RestController
@RequestMapping("/channel")
public class ChannelController extends BaseController {

    @Autowired
    private ICategoryService categoryService;

    @Autowired
    private IProductSkuService productSkuService;

    @Operation(summary = "获取首页数据")
    @GetMapping("/index")
    public AjaxResult index() {

        List<CategoryVo> levelOneCategory = categoryService.getLevelOneCategory();
        List<ProductSkuVo> topSale = productSkuService.getTopSale();
        IndexDataVo indexDataVo = new IndexDataVo();
        indexDataVo.setCategoryList(levelOneCategory);
        indexDataVo.setProductSkuList(topSale);
        return success(indexDataVo);
    }
}

3 分类接口开发

3.1 需求分析

当用户点击分类导航按钮时,展示商品分类数据,如下所示:

68627340342

3.2 接口文档

商品分类接口地址及示例数据

get  /product/channel/category
返回结果:
{
    "msg": "操作成功",
    "code": 200,
    "data": [
        {
            "id": 1,
            "name": "数码办公",
            "imageUrl": "https://lilishop-oss.oss-cn-beijing.aliyuncs.com/230f48f024a343c6be9be72597c2dcd0.png",
            "parentId": 0,
            "children": [
                {
                    "id": 2,
                    "name": "手机通讯",
                    "imageUrl": "",
                    "parentId": 1,
                    "children": [
                        {
                            "id": 3,
                            "name": "手机",
                            "imageUrl": "https://lilishop-oss.oss-cn-beijing.aliyuncs.com/1348576427264204943.png",
                            "parentId": 2,
                            "children": []
                        },
                        ...
                    ]
                }
            ]
        }
    ]
}

3.3 接口开发

3.3.1 CategoryVo添加属性

@Schema(description = "子类别")
private List<CategoryVo> children = new ArrayList<>();

3.3.2 ChannelController

@Operation(summary = "获取分类嵌套列表")
@GetMapping( "/category")
public AjaxResult category() {
    return success(categoryService.tree());
}

3.3.3 ICategoryService

List<CategoryVo> tree();

3.3.4 CategoryServiceImpl

@Override
public List<CategoryVo> tree() {
    //查询所有分类
    List<Category> allCategoryList = baseMapper.selectList(null);
    //转换为vo
    List<CategoryVo> categoryVoList = allCategoryList.stream().map(item -> {
        CategoryVo categoryVo = new CategoryVo();
        BeanUtils.copyProperties(item, categoryVo);
        return categoryVo;
    }).collect(Collectors.toList());
    //转换为树形结构
    return buildTree(categoryVoList);
}

/**
     * 使用递归方法建分类树
     * @param categoryVoList
     * @return
     */
private List<CategoryVo> buildTree(List<CategoryVo> categoryVoList) {
    //创建树形结构
    List<CategoryVo> trees = new ArrayList<>();
    //遍历所有节点
    for (CategoryVo categoryVo : categoryVoList) {
        //判断是否是根节点
        if (categoryVo.getParentId().longValue() == 0) {
            //查找当前节点的子节点,添加到树形结构中
            categoryVo = findAndAddChildren(categoryVo, categoryVoList);
            trees.add(categoryVo);
        }
    }
    return trees;
}

/**
     * 在categoryVoList中找到categoryVo的子节点,并组装到categoryVo中
     * @param categoryVo
     * @param categoryVoList
     * @return 返回组装好的categoryVo节点
     */
private CategoryVo findAndAddChildren(CategoryVo categoryVo, List<CategoryVo> categoryVoList) {
    //遍历所有节点
    for (CategoryVo node : categoryVoList) {
        //判断是否是categoryVo的子节点
        if(node.getParentId().longValue() == categoryVo.getId().longValue()) {
            //在categoryVoList中找到categoryVo的子节点,
            CategoryVo children = findAndAddChildren(node, categoryVoList);
            //并组装到categoryVo中
            categoryVo.getChildren().add(children);
        }
    }
    return categoryVo;
}

4 商品列表

4.1 需求说明

进入商品列表有三个入口:

1、点击首页一级分类

2、分类频道,点击三级分类

3、点击首页畅销商品(商品列表按销量排序展示)

搜索条件:关键字、一级分类、三级分类、品牌(获取全部品牌)

排序:销量降序、价格升序与降序

效果图如下所示:

list

要完成上述搜索功能需要完成两个接口:

1、查询所有品牌(用于商品列表页面)

2、商品列表搜索

4.2 接口文档

查询所有品牌数据接口以及示例数据:

get  /product/channel/brand
返回结果:
{
    "msg": "操作成功",
    "code": 200,
    "data": [
        {
            "id": 2,
            "name": "华为",
            "logo": "http://139.198.127.41:9000/sph/20230506/华为.png"
        },
       ...
    ]
}

商品列表搜索数据接口以及示例数据:

get  /product/channel/skuList/{pageNum}/{pageSize}?category1Id=&category3Id=&brandId=&order=1
返回结果:
{
    "total": 6,
    "rows": [
        {
            "id": 4,
            "skuName": "小米 红米Note10 5G手机 红色 + 18G",
            "thumbImg": "http://139.198.127.41:9000/spzx/20230525/665832167-5_u_1%20(1).jpg",
            "salePrice": 2999.0,
            "saleNum": 0
        },
        ...
    ],
    "code": 200,
    "msg": "查询成功"
}

4.3 商品列表搜索

4.3.1 SkuQuery

package com.spzx.product.query;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

@Data
public class SkuQuery {
    @Schema(description = "品牌id")
    private Long brandId;

    @Schema(description = "一级分类id")
    private Long category1Id;

    @Schema(description = "三级分类id")
    private Long category3Id;

    @Schema(description = "排序(销量降序:1 价格升序:2 价格降序:3)")
    private Integer order = 1;

    @Schema(description = "查询关键字")
    private String keyword;
}

4.3.2 ChannelController

/**
 * 分页条件查询商品列表
 * @param pageNum
 * @param pageSize
 * @param skuQuery
 * @return
 */
@Operation(summary = "分页条件查询商品列表")
@GetMapping("/skuList/{pageNum}/{pageSize}")
public TableDataInfo skuList(
        @PathVariable("pageNum") Integer pageNum,
        @PathVariable("pageSize") Integer pageSize,
        SkuQuery skuQuery
) {
    //1.开始分页
    PageHelper.startPage(pageNum, pageSize);
    //2.根据条件查询商品SKU列表
    List<ProductSkuVo> list = productSkuService.selectSkuList(skuQuery);
    return getDataTable(list);
}

4.3.3 IProductSkuService

/**
     * 分页条件查询商品列表
     * @param skuQuery 查询条件:关键字、分类ID、品牌ID、排序方式
     * @return
     */
List<ProductSkuVo> selectSkuList(SkuQuery skuQuery);

4.3.4 ProductSkuServiceImpl

/**
 * 分页条件查询商品列表
 * @param skuQuery 查询条件:关键字、分类ID、品牌ID、排序方式
 * @return
 */
@Override
public List<ProductSkuVo> selectSkuList(SkuQuery skuQuery) {
    return baseMapper.selectSkuList(skuQuery);
}

4.3.5 ProductSkuMapper

List<ProductSkuVo> selectSkuList(SkuQuery skuQuery);

4.3.6 ProductSkuMapper.xml

<!--分页条件查询商品列表-->
<select id="selectSkuList" resultType="com.spzx.product.vo.ProductSkuVo">
    select
        sku.id,
        sku.sku_name,
        sku.thumb_img,
        sku.sale_price,
        ss.sale_num
    from product p
    inner join product_sku sku on p.id = sku.product_id and sku.del_flag = 0
    inner join sku_stock ss on sku.id = ss.sku_id and ss.del_flag = 0
    <where>
        <if test="keyword != null and keyword != ''">
            and sku.sku_name like concat('%',#{keyword},'%')
        </if>
        <if test="brandId != null">
            and brand_id = #{brandId}
        </if>
        <if test="category1Id != null">
            and category1_id = #{category1Id}
        </if>
        <if test="category3Id != null">
            and category3_id = #{category3Id}
        </if>
        and p.del_flag = 0
    </where>
    <if test="order == 1">
        order by ss.sale_num desc
    </if>
    <if test="order == 2">
        order by sku.sale_price asc
    </if>
    <if test="order == 3">
        order by sku.sale_price desc
    </if>
</select>

4.4 查询所有品牌

ChannelController

@Autowired
private IBrandService brandService;

@Operation(summary = "获取全部品牌")
@GetMapping("/brand")
public AjaxResult selectBrandAll() {
    return success(brandService.list());
}

5 商品详情

5.1 需求分析

当点击某一个商品的时候,此时就需要在商品详情页面展示出商品的详情数据,包括:

1、商品基本信息

2、当前商品sku基本信息

3、商品sku最新价格信息

4、商品详情图片

5、商品规格信息

6、商品库存信息

item

5.2 接口文档

商品详情数据接口以及示例数据:

get  /product/channel/item/{skuId}
返回结果:
{
    "msg": "操作成功",
    "code": 200,
    "data": {
        "productSku": {
            "id": 5,
            "skuCode": "1_4",
            "skuName": "小米 红米Note10 5G手机 黑色 + 8G",
            "productId": 1,
            "thumbImg": "http://139.198.127.41:9000/spzx/20230525/665832167-1_u_1.jpg",
            "salePrice": 1999.00,
            "marketPrice": 2019.00,
            "costPrice": 1599.00,
            "skuSpec": "黑色 + 8G",
            "weight": 1.00,
            "volume": 1.00,
            "status": 1,
            "stockNum": null,
            "saleNum": null
        },
        "skuPrice": {
            "skuId": 5,
            "salePrice": 1999.00,
            "marketPrice": 2019.00
        },
        "skuStock": {
            "skuId": 5,
            "availableNum": 98,
            "saleNum": 2
        },
        "sliderUrlList": [
            "http://139.198.127.41:9000/spzx/20230525/665832167-5_u_1.jpg",
            ...
        ],
        "detailsImageUrlList": [
            "http://139.198.127.41:9000/spzx/20230525/665832167-5_u_1.jpg",
            ...
        ],
        "specValueList": [
            {
                "valueList": [
                    "白色",
                    "红色",
                    "黑色"
                ],
                "key": "颜色"
            },
            {
                "valueList": [
                    "8G",
                    "18G"
                ],
                "key": "内存"
            }
        ],
        "skuSpecValueMap": {
            "黑色 + 18G": 6,
            "红色 + 18G": 4,
            "白色 + 8G": 1,
            "白色 + 18G": 2,
            "黑色 + 8G": 5,
            "红色 + 8G": 3
        }
    }
}

5.3 获取价格信息

5.3.1 创建SkuPriceVo

package com.spzx.product.vo;

@Schema(description = "sku价格")
@Data
public class SkuPriceVo {

    @Schema(description = "skuId")
    private Long skuId;

    @Schema(description = "售价")
    private BigDecimal salePrice;

    @Schema(description = "市场价")
    private BigDecimal marketPrice;
}

5.3.2 IProductSkuService

SkuPriceVo getSkuPrice(Long skuId);

5.3.3 ProductSkuServiceImpl

@Override
public SkuPriceVo getSkuPrice(Long skuId) {

    // 根据skuId查询商品价格
    LambdaQueryWrapper<ProductSku> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper
        .eq(ProductSku::getId, skuId)
        .select(ProductSku::getSalePrice, ProductSku::getMarketPrice);
    ProductSku productSku = baseMapper.selectOne(queryWrapper);
    // 封装数据
    SkuPriceVo skuPriceVo = new SkuPriceVo();
    skuPriceVo.setMarketPrice(productSku.getMarketPrice());
    skuPriceVo.setSalePrice(productSku.getSalePrice());
    skuPriceVo.setSkuId(skuId);
    return skuPriceVo;
}

5.4 获取库存信息

5.4.1 创建SkuStockVo

package com.spzx.product.vo;

@Schema(description = "sku库存")
@Data
public class SkuStockVo {
    @Schema(description = "skuId")
    private Long skuId;

    @Schema(description = "可用库存数")
    private Integer availableNum;

    @Schema(description = "销量")
    private Integer saleNum;
}

5.4.2 ISkuStockService

SkuStockVo getSkuStock(Long skuId);

5.4.3 SkuStockServiceImpl

@Override
public SkuStockVo getSkuStock(Long skuId) {

    // 根据skuId查询商品库存
    LambdaQueryWrapper<SkuStock> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper
            .eq(SkuStock::getSkuId, skuId)
            .select(SkuStock::getAvailableNum, SkuStock::getSaleNum);
    SkuStock skuStock = baseMapper.selectOne(queryWrapper);
    // 封装数据
    SkuStockVo skuStockVo = new SkuStockVo();
    skuStockVo.setAvailableNum(skuStock.getAvailableNum());
    skuStockVo.setSaleNum(skuStock.getSaleNum());
    skuStockVo.setSkuId(skuId);
    return skuStockVo;
}

5.5 获取商品详情

5.5.1 IProductDetailsService

String[] getProductDetails(Long id);

5.5.2 ProductDetailsServiceImpl

@Override
public String[] getProductDetails(Long id) {
    // 根据商品id查询商品详情
    LambdaQueryWrapper<ProductDetails> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper
        .eq(ProductDetails::getProductId, id)
        .select(ProductDetails::getImageUrls);
    ProductDetails productDetails = baseMapper.selectOne(queryWrapper);
    // 返回商品详情的图片地址列表
    return productDetails.getImageUrls().split(",");
}

5.6 获取商品规格

5.6.1 IProductSkuService

Map<String, Long> getSkuSpecValue(Long id);

5.6.2 ProductSkuServiceImpl

@Override
public Map<String, Long> getSkuSpecValue(Long id) {
    // 查询一个商品的所有sku
    LambdaQueryWrapper<ProductSku> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper
        .eq(ProductSku::getProductId, id)
        .select(ProductSku::getId, ProductSku::getSkuSpec);
    List<ProductSku> productSkuList = baseMapper.selectList(queryWrapper);
    // 封装数据
    Map<String,Long> skuSpecValueMap = new HashMap<>();
    productSkuList.forEach(item -> {
        skuSpecValueMap.put(item.getSkuSpec(), item.getId());
    });
    return skuSpecValueMap;
}

5.7 接口开发

5.7.1 ItemVo

package com.spzx.product.vo;

import com.alibaba.fastjson2.JSONArray;
import com.spzx.product.domain.Product;
import com.spzx.product.domain.ProductSku;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

import java.util.List;
import java.util.Map;

@Data
@Schema(description = "商品详情")
public class ItemVo {

    @Schema(description = "商品sku信息")
    private ProductSku productSku;
    
    @Schema(description = "最新价格信息")
    private SkuPriceVo skuPrice;

    @Schema(description = "商品库存信息")
    private SkuStockVo skuStock;

    @Schema(description = "商品轮播图列表")
    private String[] sliderUrlList;

    @Schema(description = "商品规格信息")
    private JSONArray specValueList;
    
    @Schema(description = "商品详情图片列表")
    private String[] detailsImageUrlList;
    
    @Schema(description = "商品规格对应商品skuId信息")
    private Map<String, Long> skuSpecValueMap;
}

5.7.2 ChannelController

@Autowired
private IProductService productService;
@Autowired
private ISkuStockService skuStockService;
@Autowired
private IProductDetailsService productDetailsService;

@Operation(summary = "商品详情")
@GetMapping("/item/{skuId}")
public AjaxResult item(
    @Parameter(description = "skuId")
    @PathVariable Long skuId) {

    ItemVo itemVo = new ItemVo();

    //获取sku信息
    ProductSku productSku = productSkuService.getById(skuId);
    itemVo.setProductSku(productSku);

    //获取商品最新价格
    SkuPriceVo skuPrice = productSkuService.getSkuPrice(skuId);
    itemVo.setSkuPrice(skuPrice);

    //获取商品库存信息
    SkuStockVo skuStock = skuStockService.getSkuStock(skuId);
    itemVo.setSkuStock(skuStock);

    //获取商品id
    Long productId = productSku.getProductId();

    //获取商品信息
    Product product = productService.getById(productId);
    //商品信息:轮播图列表
    String[] sliderUrlList = product.getSliderUrls().split(",");
    itemVo.setSliderUrlList(sliderUrlList);
    //商品信息:商品规格列表
    JSONArray specValueList = JSON.parseArray(product.getSpecValue());
    itemVo.setSpecValueList(specValueList);

    //获取商品详情图片
    String[] detailsImageUrlList = productDetailsService.getProductDetails(productId);
    itemVo.setDetailsImageUrlList(detailsImageUrlList);

    //获取商品规格Map
    Map<String, Long> skuSpecValueMap = productSkuService.getSkuSpecValue(productId);
    itemVo.setSkuSpecValueMap(skuSpecValueMap);

    return success(itemVo);
}