[TOC]
项目演示地址:http://ry-spzx.atguigu.cn/
前端H5部分我们不需要开发,只需要根据接口文档开发微服务接口,然后对接到写好的前端H5即可
在资料中找到nginx压缩包
,解压到非中文目录下,修改配置文件conf/nginx.conf
,
listen 81;
打开命令行,进入到nginx目录,执行以下命令运行nginx:
#启动
nginx.exe
#停止
nginx.exe -s stop
#重新加载配置
nginx.exe -s reload
在资料中找到app前端打包后的代码.zip
,解压到nginx的html
目录下:
浏览器访问http://localhost:81
访问移动端前端http://localhost:81/#/pages/set/set,选择右上角`配置`按钮
跳转到设置页面,点击“接口base路径”,修改baseUrl为本地的微服务网关地址:http://localhost:8080
注意:如果删除浏览器历史记录,这个baseUrl需要重新配置
访问首页测试,发现跳转到了登录页面。
原因:接口的远程访问被网关服务拦截了,AuthFilter
中校验了令牌
移动端首页、列表页、详情页无需登录即可访问,因此可以将这些页面放入网关白名单
在nacos的spzx-gateway-dev.yml
中配置网关白名单:
# 不校验白名单
ignore:
whites:
...
- /product/channel/**
白名单配置的加载在网关模块spzx-gateway的IgnoreWhiteProperties
中,设置了注解@RefreshScope
,因此可以热更新
首页展示商品一级分类数据以及畅销商品列表数据,如下所示:
首页接口地址及示例数据
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
},
...
]
}
}
查询category表,获取parent_id="0"的数据列表
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;
}
List<CategoryVo> getLevelOneCategory();
@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());
}
查询productSku表和stock库存表,根据sku_stock表sale_num字段排序,取前20条数据列表,我们通过自定义sql来实现
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;
}
List<ProductSkuVo> getTopSale();
@Override
public List<ProductSkuVo> getTopSale() {
return baseMapper.selectTopSale();
}
List<ProductSkuVo> selectTopSale();
<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>
封装首页数据
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;
}
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);
}
}
当用户点击分类导航按钮时,展示商品分类数据,如下所示:
商品分类接口地址及示例数据
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": []
},
...
]
}
]
}
]
}
@Schema(description = "子类别")
private List<CategoryVo> children = new ArrayList<>();
@Operation(summary = "获取分类嵌套列表")
@GetMapping( "/category")
public AjaxResult category() {
return success(categoryService.tree());
}
List<CategoryVo> tree();
@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;
}
进入商品列表有三个入口:
1、点击首页一级分类
2、分类频道,点击三级分类
3、点击首页畅销商品(商品列表按销量排序展示)
搜索条件:关键字、一级分类、三级分类、品牌(获取全部品牌)
排序:销量降序、价格升序与降序
效果图如下所示:
要完成上述搜索功能需要完成两个接口:
1、查询所有品牌(用于商品列表页面)
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": "查询成功"
}
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;
}
/**
* 分页条件查询商品列表
* @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);
}
/**
* 分页条件查询商品列表
* @param skuQuery 查询条件:关键字、分类ID、品牌ID、排序方式
* @return
*/
List<ProductSkuVo> selectSkuList(SkuQuery skuQuery);
/**
* 分页条件查询商品列表
* @param skuQuery 查询条件:关键字、分类ID、品牌ID、排序方式
* @return
*/
@Override
public List<ProductSkuVo> selectSkuList(SkuQuery skuQuery) {
return baseMapper.selectSkuList(skuQuery);
}
List<ProductSkuVo> selectSkuList(SkuQuery skuQuery);
<!--分页条件查询商品列表-->
<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>
@Autowired
private IBrandService brandService;
@Operation(summary = "获取全部品牌")
@GetMapping("/brand")
public AjaxResult selectBrandAll() {
return success(brandService.list());
}
当点击某一个商品的时候,此时就需要在商品详情页面展示出商品的详情数据,包括:
1、商品基本信息
2、当前商品sku基本信息
3、商品sku最新价格信息
4、商品详情图片
5、商品规格信息
6、商品库存信息
商品详情数据接口以及示例数据:
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
}
}
}
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;
}
SkuPriceVo getSkuPrice(Long skuId);
@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;
}
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;
}
SkuStockVo getSkuStock(Long skuId);
@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;
}
String[] getProductDetails(Long id);
@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(",");
}
Map<String, Long> getSkuSpecValue(Long id);
@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;
}
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;
}
@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);
}