[TOC] # 第3章 移动端数据展示 项目演示地址:http://ry-spzx.atguigu.cn/ ## 1 前端部署 前端H5部分我们不需要开发,只需要根据接口文档开发微服务接口,然后对接到写好的前端H5即可 ### 1.1 启动nginx 在资料中找到`nginx压缩包`,解压到非中文目录下,修改配置文件`conf/nginx.conf`, ```properties listen 81; ``` 打开命令行,进入到nginx目录,执行以下命令运行nginx: ```bash #启动 nginx.exe #停止 nginx.exe -s stop #重新加载配置 nginx.exe -s reload ``` ### 1.2 部署h5程序 在资料中找到`app前端打包后的代码.zip`,解压到nginx的`html`目录下: ![image-20241111031950403](images/image-20241111031950403.png) 浏览器访问http://localhost:81 ### 1.3 接口设置 访问移动端前端http://localhost:81/#/pages/set/set,选择右上角`配置`按钮 ![image-20241115154314926](images/image-20241115154314926-17316773295401.png) 跳转到设置页面,点击“接口base路径”,修改baseUrl为本地的微服务网关地址:http://localhost:8080 **注意:如果删除浏览器历史记录,这个baseUrl需要重新配置** ![image-20241115154116976](images/image-20241115154116976-17316773295402.png) ### 1.4 配置白名单 访问首页测试,发现跳转到了登录页面。 原因:接口的远程访问被网关服务拦截了,`AuthFilter`中校验了令牌 移动端首页、列表页、详情页无需登录即可访问,因此可以将这些页面放入网关白名单 在nacos的`spzx-gateway-dev.yml`中配置网关白名单: ```yaml # 不校验白名单 ignore: whites: ... - /product/channel/** ``` 白名单配置的加载在网关模块spzx-gateway的`IgnoreWhiteProperties`中,设置了注解`@RefreshScope`,因此可以热更新 ## 2 首页接口开发 ### 2.1 需求分析 首页展示商品一级分类数据以及畅销商品列表数据,如下所示: ![image-20230703225931377](images/image-20230703225931377.png) ### 2.2 接口文档 首页接口地址及示例数据 ```json 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 ```java 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 ```java List getLevelOneCategory(); ``` #### 2.3.3 CategoryServiceImpl ```java @Override public List getLevelOneCategory() { //查询所有一级分类 List allCategoryList = baseMapper.selectList( new LambdaQueryWrapper().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 ```java 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 ```java List getTopSale(); ``` #### 2.4.3 ProductSkuServiceImpl ```java @Override public List getTopSale() { return baseMapper.selectTopSale(); } ``` #### 2.4.4 ProductSkuMapper ```java List selectTopSale(); ``` #### 2.4.5 ProductSkuMapper.xml ```xml ``` ### 2.5 接口开发 #### 2.5.1 创建IndexDataVo 封装首页数据 ```java package com.spzx.product.vo; @Schema(description = "首页数据") @Data public class IndexDataVo { @Schema(description = "商品分类VO列表") private List categoryList; @Schema(description = "商品skuVO列表") private List productSkuList; } ``` #### 2.5.2 创建ChannelController ```java 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 levelOneCategory = categoryService.getLevelOneCategory(); List topSale = productSkuService.getTopSale(); IndexDataVo indexDataVo = new IndexDataVo(); indexDataVo.setCategoryList(levelOneCategory); indexDataVo.setProductSkuList(topSale); return success(indexDataVo); } } ``` ## 3 分类接口开发 ### 3.1 需求分析 当用户点击分类导航按钮时,展示商品分类数据,如下所示: 68627340342 ### 3.2 接口文档 商品分类接口地址及示例数据 ```json 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添加属性 ```java @Schema(description = "子类别") private List children = new ArrayList<>(); ``` #### 3.3.2 ChannelController ```java @Operation(summary = "获取分类嵌套列表") @GetMapping( "/category") public AjaxResult category() { return success(categoryService.tree()); } ``` #### 3.3.3 ICategoryService ```java List tree(); ``` #### 3.3.4 CategoryServiceImpl ```java @Override public List tree() { //查询所有分类 List allCategoryList = baseMapper.selectList(null); //转换为vo List 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 buildTree(List categoryVoList) { //创建树形结构 List 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 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 接口文档 查询所有品牌数据接口以及示例数据: ```json get /product/channel/brand 返回结果: { "msg": "操作成功", "code": 200, "data": [ { "id": 2, "name": "华为", "logo": "http://139.198.127.41:9000/sph/20230506/华为.png" }, ... ] } ``` 商品列表搜索数据接口以及示例数据: ```json 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 ```java 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 ```java /** * 分页条件查询商品列表 * @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 list = productSkuService.selectSkuList(skuQuery); return getDataTable(list); } ``` #### 4.3.3 IProductSkuService ```java /** * 分页条件查询商品列表 * @param skuQuery 查询条件:关键字、分类ID、品牌ID、排序方式 * @return */ List selectSkuList(SkuQuery skuQuery); ``` #### 4.3.4 ProductSkuServiceImpl ```java /** * 分页条件查询商品列表 * @param skuQuery 查询条件:关键字、分类ID、品牌ID、排序方式 * @return */ @Override public List selectSkuList(SkuQuery skuQuery) { return baseMapper.selectSkuList(skuQuery); } ``` #### 4.3.5 ProductSkuMapper ```java List selectSkuList(SkuQuery skuQuery); ``` #### 4.3.6 ProductSkuMapper.xml ```xml ``` ### 4.4 查询所有品牌 #### ChannelController ```java @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 接口文档 商品详情数据接口以及示例数据: ```json 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 ```java 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 ```java SkuPriceVo getSkuPrice(Long skuId); ``` #### 5.3.3 ProductSkuServiceImpl ```java @Override public SkuPriceVo getSkuPrice(Long skuId) { // 根据skuId查询商品价格 LambdaQueryWrapper 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 ```java 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 ```java SkuStockVo getSkuStock(Long skuId); ``` #### 5.4.3 SkuStockServiceImpl ```java @Override public SkuStockVo getSkuStock(Long skuId) { // 根据skuId查询商品库存 LambdaQueryWrapper 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 ```java String[] getProductDetails(Long id); ``` #### 5.5.2 ProductDetailsServiceImpl ```java @Override public String[] getProductDetails(Long id) { // 根据商品id查询商品详情 LambdaQueryWrapper 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 ```java Map getSkuSpecValue(Long id); ``` #### 5.6.2 ProductSkuServiceImpl ```java @Override public Map getSkuSpecValue(Long id) { // 查询一个商品的所有sku LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper .eq(ProductSku::getProductId, id) .select(ProductSku::getId, ProductSku::getSkuSpec); List productSkuList = baseMapper.selectList(queryWrapper); // 封装数据 Map skuSpecValueMap = new HashMap<>(); productSkuList.forEach(item -> { skuSpecValueMap.put(item.getSkuSpec(), item.getId()); }); return skuSpecValueMap; } ``` ### 5.7 接口开发 #### 5.7.1 ItemVo ```java 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 skuSpecValueMap; } ``` #### 5.7.2 ChannelController ```java @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 skuSpecValueMap = productSkuService.getSkuSpecValue(productId); itemVo.setSkuSpecValueMap(skuSpecValueMap); return success(itemVo); } ```