学习目标:
商品详情所需构建的数据如下:
1,Sku基本信息
2,Sku图片信息
3,Sku分类信息
4,Sku销售属性相关信息
5,Sku价格信息(平台可以单独修改价格,sku后续会放入缓存,为了回显最新价格,所以单独获取)
6,展示商品的海报
7,获取skuId 对应的商品规格参数
...
模块规划思路:
1,service-item微服务模块封装详情页面所需数据接口;
2,service-item通过feign client调用其他微服务数据接口进行数据汇总;
3,pc端前台页面通过gmall-web调用service-item数据接口渲染页面;
4,service-item可以为pc端、H5、安卓与ios等前端应用提供数据接口,gmall-web为pc端页面渲染形式
5,service-item获取商品信息需要调用service-product服务sku信息等;
6,由于service各微服务可能会相互调用,调用方式都是通过feign client调用,所以我们把feign client api接口单独封装出来,需要时直接引用feign client api模块接口即可,即需创建service-client父模块,管理各service微服务feign client api接口;
首先我们一起来分析一下,在商品详情页面中需要哪些数据,通过web前端项目item/item.html
模板页面中的Thymeleaf语法得知,例如有下面的部分:
逐个查看,发现需要下面的变量:
因此接下来,我们需要在商品微服务补充RestFul接口实现 并且 提供远程调用Feign API接口给商品详情微服务来调用获取。
在service-product
微服务提供restful接口实现以下接口均为微服务模块内部之间的调用。
YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/587
在service-product
模块中新建包:com.atguigu.gmall.product.api 这个包下提供服务内部调用的restful 接口实现
package com.atguigu.gmall.product.api;
import com.atguigu.gmall.product.model.SkuInfo;
import com.atguigu.gmall.product.service.SkuManageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author: atguigu
* @create: 2023-07-28 09:37
*/
@RestController
@RequestMapping("/api/product/")
public class ProductApiController {
@Autowired
private SkuManageService skuManageService;
/**
* 根据商品SKUID查询商品基本信息以及SKU相关图片列表
* @param skuId
* @return
*/
@GetMapping("/inner/getSkuInfo/{skuId}")
public SkuInfo getSkuInfo(@PathVariable("skuId") Long skuId){
return skuManageService.getSkuInfo(skuId);
}
}
SkuManageService
/**
* 根据商品SKUID查询商品基本信息以及SKU相关图片列表
* @param skuId
* @return
*/
SkuInfo getSkuInfo(Long skuId);
SkuManageServiceImpl
/**
* 根据商品SKUID查询商品基本信息以及SKU相关图片列表
*
* @param skuId
* @return
*/
@Override
public SkuInfo getSkuInfo(Long skuId) {
//1.根据skuId主键查询商品SKU信息
SkuInfo skuInfo = skuInfoService.getById(skuId);
//2.根据SkuID查询商品图片列表
if (skuInfo != null) {
LambdaQueryWrapper<SkuImage> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SkuImage::getSkuId, skuId);
List<SkuImage> skuImageList = skuImageService.list(queryWrapper);
skuInfo.setSkuImageList(skuImageList);
}
return skuInfo;
}
访问Knife4j接口文档进行测试:
YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/635
sku是挂在三级分类下面的,我们的分类信息分别在base_category1、base_category2、base_category3这三张表里面,目前需要通过sku表的三级分类id获取一级分类名称、二级分类名称和三级分类名称
解决方案:
我们可以建立一个视图(view),把三张表关联起来,视图id就是三级分类id,这样通过三级分类id就可以查询到相应数据,效果如下:
创建视图
CREATE
OR REPLACE VIEW base_category_view AS SELECT
bc3.id id,
bc1.id category1_id,
bc1.NAME category1_name,
bc2.id category2_id,
bc2.NAME category2_name,
bc3.id category3_id,
bc3.NAME category3_name
FROM
base_category1 bc1
INNER JOIN base_category2 bc2 ON bc2.category1_Id = bc1.id
INNER JOIN base_category3 bc3 ON bc3.category2_id = bc2.id
WHERE
bc1.is_deleted = 0
AND bc2.is_deleted = 0
AND bc3.is_deleted = 0
ProductApiController
/**
* 根据三级分类ID查询三级分类信息
*
* @param category3Id
* @return
*/
@ApiOperation("根据三级分类ID查询三级分类信息")
@GetMapping("/inner/getCategoryView/{category3Id}")
public BaseCategoryView getCategoryView(@PathVariable("category3Id") Long category3Id) {
return baseCategoryViewService.getById(category3Id);
}
BaseCategoryViewService接口
package com.atguigu.gmall.product.service;
import com.atguigu.gmall.product.model.BaseCategoryView;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* VIEW 业务接口类
* @author atguigu
* @since 2023-06-10
*/
public interface BaseCategoryViewService extends IService<BaseCategoryView> {
}
BaseCategoryViewServiceImpl
package com.atguigu.gmall.product.service.impl;
import com.atguigu.gmall.product.model.BaseCategoryView;
import com.atguigu.gmall.product.mapper.BaseCategoryViewMapper;
import com.atguigu.gmall.product.service.BaseCategoryViewService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* VIEW 业务实现类
*
* @author atguigu
* @since 2023-06-10
*/
@Service
public class BaseCategoryViewServiceImpl extends ServiceImpl<BaseCategoryViewMapper, BaseCategoryView> implements BaseCategoryViewService {
}
package com.atguigu.gmall.product.mapper;
import com.atguigu.gmall.product.model.BaseCategoryView;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* VIEW Mapper 接口
*
* @author atguigu
* @since 2023-06-10
*/
public interface BaseCategoryViewMapper extends BaseMapper<BaseCategoryView> {
}
测试:
YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/563
ProductApiController
/**
* 根据商品SkuID查询商品实时价格
*
* @param skuId
* @return
*/
@ApiOperation("根据商品SkuID查询商品实时价格")
@GetMapping("/inner/getSkuPrice/{skuId}")
public BigDecimal getSkuPrice(@PathVariable("skuId") Long skuId) {
return skuManageService.getSkuPrice(skuId);
}
SkuManageService
/**
* 根据商品SkuID查询商品实时价格
*
* @param skuId
* @return
*/
BigDecimal getSkuPrice(Long skuId);
SkuManageServiceImpl
/**
* 根据商品SkuID查询商品实时价格
*
* @param skuId
* @return
*/
@Override
public BigDecimal getSkuPrice(Long skuId) {
LambdaQueryWrapper<SkuInfo> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SkuInfo::getId, skuId);
queryWrapper.select(SkuInfo::getPrice);
SkuInfo skuInfo = skuInfoService.getOne(queryWrapper);
if (skuInfo != null) {
return skuInfo.getPrice();
}
return new BigDecimal("0.0");
}
YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/603
ProductApiController
/**
* 根据商品spuId查询商品海报图片列表
*
* @param spuId
* @return
*/
@ApiOperation("根据商品spuId查询商品海报图片列表")
@GetMapping("/inner/findSpuPosterBySpuId/{spuId}")
public List<SpuPoster> getSpuPosterBySpuId(@PathVariable("spuId") Long spuId) {
return spuManageService.getSpuPosterBySpuId(spuId);
}
SpuManageService接口
/**
* 根据商品spuId查询商品海报图片列表
*
* @param spuId
* @return
*/
List<SpuPoster> getSpuPosterBySpuId(Long spuId);
SpuManageServiceImpl实现类
/**
* 根据商品spuId查询商品海报图片列表
*
* @param spuId
* @return
*/
@Override
public List<SpuPoster> getSpuPosterBySpuId(Long spuId) {
LambdaQueryWrapper<SpuPoster> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SpuPoster::getSpuId, spuId);
return spuPosterService.list(queryWrapper);
}
需求:显示在商品详情规格处
YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/555
ProductApiController
/**
* 根据商品SkuID查询sku商品所有属性名称以及属性值
*
* @param skuId
* @return
*/
@ApiOperation("根据商品SkuID查询sku商品所有属性名称以及属性值")
@GetMapping("/inner/getAttrList/{skuId}")
public List<BaseAttrInfo> getAttrList(@PathVariable("skuId") Long skuId) {
return skuManageService.getAttrList(skuId);
}
BaseAttrInfo实体类中增加attrValue属性
package com.atguigu.gmall.product.model;
@Data
@ApiModel(description = "平台属性")
@TableName("base_attr_info")
public class BaseAttrInfo extends BaseEntity {
//...省略其他字段
/**
* 当前商品SKU中对应属性值 包含属性值名称
*/
@TableField(exist = false)
private String attrValue;
}
SkuManageService接口
/**
* 根据商品SkuID查询sku商品所有属性名称以及属性值
*
* @param skuId
* @return
*/
List<BaseAttrInfo> getAttrList(Long skuId);
SkuManageServiceImpl实现类
/**
* 根据商品SkuID查询sku商品所有属性名称以及属性值
*
* @param skuId
* @return
*/
@Override
public List<BaseAttrInfo> getAttrList(Long skuId) {
SkuAttrValueMapper skuAttrValueBaseMapper = (SkuAttrValueMapper) skuAttrValueService.getBaseMapper();
return skuAttrValueBaseMapper.getAttrList(skuId);
}
注意:BaseAttrInfo实体类中增加属性值名称变量:attrValue
BaseAttrInfoMapper
package com.atguigu.gmall.product.mapper;
import com.atguigu.gmall.product.model.BaseAttrInfo;
import com.atguigu.gmall.product.model.SkuAttrValue;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* sku平台属性值关联表 Mapper 接口
*
* @author atguigu
* @since 2023-07-26
*/
public interface SkuAttrValueMapper extends BaseMapper<SkuAttrValue> {
/**
* 查询指定SKU商品所有平台属性以及属性值
* @param skuId
* @return
*/
List<BaseAttrInfo> getAttrList(@Param("skuId") Long skuId);
}
SkuAttrValueMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.gmall.product.mapper.SkuAttrValueMapper">
<!--查询指定SKU商品所有平台属性以及属性值-->
<select id="getAttrList" resultType="com.atguigu.gmall.product.model.BaseAttrInfo">
SELECT
bai.id,
bai.attr_name,
bai.category_id,
bai.category_level,
bav.id base_attr_value_id,
bav.value_name attr_value
FROM
base_attr_info bai
INNER JOIN sku_attr_value sav ON sav.attr_id = bai.id
INNER JOIN base_attr_value bav ON sav.value_id = bav.id
WHERE
sav.sku_id = #{skuId} and bai.is_deleted = 0 and sav.is_deleted = 0 and bav.is_deleted = 0
</select>
</mapper>
思路:
1、查出该商品的spu的所有销售属性和属性值
2、标识出本商品对应的销售属性
3、点击其他销售属性值的组合,跳转到另外的sku页面
YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/611
SQL实现
#需求:查询所有商品销售属性以及属性值;基于当前SKU拥有销售属性实现前端默认选中销售效果(能够让前端判断出哪一组销售属性是被选中的)
#第一步:关联查询spu销售属性跟销售属性值得到所有商品销售属性信息
SELECT
ssa.id,
ssa.spu_id,
ssa.base_sale_attr_id,
ssa.sale_attr_name,
ssav.id,
ssav.sale_attr_value_name
FROM
spu_sale_attr ssa
INNER JOIN spu_sale_attr_value ssav ON ssa.spu_id = ssav.spu_id
AND ssa.base_sale_attr_id = ssav.base_sale_attr_id
WHERE
ssa.spu_id = 10;
#第二步:基于当前SKU拥有销售属性实现前端默认选中销售效果(能够让前端判断出哪一组销售属性是被选中的)- 如何判断哪个销售属性被选中
# 2.1 单独获取某个SKU销售属性信息
select * from sku_sale_attr_value where sku_id = 24; #冰霜银+128G
select * from sku_sale_attr_value where sku_id = 25; #冰霜银+256G
#第三步:将第一步查询结果当成一张表(第1步中执行查询所有销售属性以及值)作为左表,将第二步sku销售属性使用表作为右表
SELECT
ssa.id,
ssa.spu_id,
ssa.base_sale_attr_id,
ssa.sale_attr_name,
ssav.id,
ssav.sale_attr_value_name,
if(s.id is null, '0', '1') is_checked
FROM
spu_sale_attr ssa
INNER JOIN spu_sale_attr_value ssav ON ssa.spu_id = ssav.spu_id
AND ssa.base_sale_attr_id = ssav.base_sale_attr_id and ssa.spu_id = 10 and ssa.is_deleted = '0' and ssav.is_deleted='0'
left join sku_sale_attr_value s on s.sale_attr_value_id = ssav.id and s.is_deleted='0' and s.sku_id = 25
order by ssa.id, ssav.id;
ProductApiController
/**
* 根据spuId查询所有销售属性以及属性值,根据skuId得到当前sku选中销售属性
*
* @param skuId
* @param spuId
* @return
*/
@ApiOperation("根据spuId查询所有销售属性以及属性值,根据skuId得到当前sku选中销售属性")
@GetMapping("/inner/getSpuSaleAttrListCheckBySku/{skuId}/{spuId}")
public List<SpuSaleAttr> getSpuSaleAttrListCheckBySku(@PathVariable("skuId") Long skuId, @PathVariable("spuId") Long spuId) {
return spuManageService.getSpuSaleAttrListCheckBySku(skuId, spuId);
}
SkuManageService接口
/**
* 根据spuId查询所有销售属性以及属性值,根据skuId得到当前sku选中销售属性
*
* @param skuId
* @param spuId
* @return
*/
List<SpuSaleAttr> getSpuSaleAttrListCheckBySku(Long skuId, Long spuId);
SkuManageServiceImpl实现类
/**
* 根据spuId查询所有销售属性以及属性值,根据skuId得到当前sku选中销售属性
*
* @param skuId
* @param spuId
* @return
*/
@Override
public List<SpuSaleAttr> getSpuSaleAttrListCheckBySku(Long skuId, Long spuId) {
SpuSaleAttrMapper spuSaleAttrMapper = (SpuSaleAttrMapper) spuSaleAttrService.getBaseMapper();
return spuSaleAttrMapper.getSpuSaleAttrListCheckBySku(skuId, spuId);
}
第1、2条通过此sql实现
SELECT
ssa.id,
ssa.spu_id,
ssa.base_sale_attr_id,
ssa.sale_attr_name,
ssav.id spu_sale_attr_value_id,
ssav.sale_attr_value_name,
IF( s.id IS NULL, '0', '1' ) is_checked
FROM
spu_sale_attr ssa
INNER JOIN spu_sale_attr_value ssav ON ssa.spu_id = ssav.spu_id
AND ssa.base_sale_attr_id = ssav.base_sale_attr_id
AND ssa.spu_id = #{spuId}
AND ssa.is_deleted = '0'
AND ssav.is_deleted = '0'
LEFT JOIN sku_sale_attr_value s ON s.sale_attr_value_id = ssav.id
AND s.is_deleted = '0'
AND s.sku_id = #{skuId}
ORDER BY ssa.id , ssav.id
此sql列出所有该spu的销售属性和属性值,并关联某skuid如果能关联上is_check设为1,否则设为0。
在对应的实体类中【SpuSaleAttrValue】添加属性字段
@TableField(exist = false)
String isChecked;
package com.atguigu.gmall.product.mapper;
import com.atguigu.gmall.product.model.SpuSaleAttr;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* spu销售属性 Mapper 接口
*
* @author atguigu
* @since 2023-07-25
*/
public interface SpuSaleAttrMapper extends BaseMapper<SpuSaleAttr> {
/**
* 根据spuId查询所有销售属性以及属性值,根据skuId得到当前sku选中销售属性
* @param skuId
* @param spuId
* @return
*/
List<SpuSaleAttr> getSpuSaleAttrListCheckBySku(@Param("skuId") Long skuId, @Param("spuId") Long spuId);
}
SpuSaleAttrMapper.xml 注意:复用以前的结果集,要保证销售属性值ID别名跟以前ResultMap里配置一样
<!--自定义结果集:封装销售顺序跟销售属性值一对多 -->
<resultMap id="spuSaleAttrMap" type="com.atguigu.gmall.product.model.SpuSaleAttr" autoMapping="true">
<id column="id" property="id"/>
<!--销售属性值集合-->
<collection property="spuSaleAttrValueList" ofType="com.atguigu.gmall.product.model.SpuSaleAttrValue" autoMapping="true">
<id column="spu_sale_attr_value_id" property="id"/>
</collection>
</resultMap>
<!--根据spuId查询所有销售属性以及属性值,根据skuId得到当前sku选中销售属性 复用以前写好的结果集spuSaleAttrMap-->
<select id="getSpuSaleAttrListCheckBySku" resultMap="spuSaleAttrMap">
SELECT
ssa.id,
ssa.spu_id,
ssa.base_sale_attr_id,
ssa.sale_attr_name,
ssav.id spu_sale_attr_value_id,
ssav.sale_attr_value_name,
IF( s.id IS NULL, '0', '1' ) is_checked
FROM
spu_sale_attr ssa
INNER JOIN spu_sale_attr_value ssav ON ssa.spu_id = ssav.spu_id
AND ssa.base_sale_attr_id = ssav.base_sale_attr_id
AND ssa.spu_id = #{spuId}
AND ssa.is_deleted = '0'
AND ssav.is_deleted = '0'
LEFT JOIN sku_sale_attr_value s ON s.sale_attr_value_id = ssav.id
AND s.is_deleted = '0'
AND s.sku_id = #{skuId}
ORDER BY ssa.id , ssav.id
</select>
YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/595
实现思路:
1 、从页面中获得得所有选中的销售属性进行组合比如:
“属性值1|属性值2” 用这个字符串匹配一个对照表,来获得skuId。并进行跳转,或者告知无货。
2、后台要生成一个“属性值1|属性值2:skuId”的一个json串以提供页面进行匹配。如
3、需要从后台数据库查询出该spu下的所有skuId和属性值关联关系。然后加工成如上的Json串,用该json串,跟前台匹配。
实现:
使用sql 语句来解决:
GROUP_CONCAT:group_concat( [distinct] 要连接的字段 [order by 排序字段 asc/desc ] [separator '分隔符'] )
#前端要求:{"3732|3734":"24", "3732|3735":"25", "3733|3734":"26", "3733|3735":"27"}
#第一步:根据spuId查询spu所有销售属性值ID spuId=10商品包含4个SKU,每个SKU都有2个销售属性
select * from sku_sale_attr_value where spu_id = 10;
#第二步:对上面SQL,根据skuID进行分组
#严格模式only_full_group_by规则 在select中只能出现group by分组字段或者聚合函数
select * from sku_sale_attr_value ssav where spu_id = 10 group by ssav.sku_id;
#故改正-只得到分组后skuID,发现没意义
select ssav.sku_id from sku_sale_attr_value ssav where spu_id = 10 group by ssav.sku_id;
#希望对skuID分组后,再将分组后销售属性记录中销售属性值ID采用指定符号进行拼接
#第三步:整的语法如下:group_concat([DISTINCT] 要连接的字段 [Order BY ASC/DESC 排序字段] [Separator '分隔符'])
SELECT
group_concat(ssav.sale_attr_value_id separator '|') value_ids,
ssav.sku_id
FROM
sku_sale_attr_value ssav
where spu_id = 10
GROUP BY
ssav.sku_id;
#第四步:验证保证 销售属性值顺序 必须按照 先根据销售属性ID排序,再按照销售属性值ID进行排序 跟加载销售属性一致
SELECT
group_concat(ssav.sale_attr_value_id order by ssa.id,ssav.sale_attr_value_id separator '|') value_ids,
ssav.sku_id
FROM
sku_sale_attr_value ssav left join spu_sale_attr_value s on ssav.sale_attr_value_id = s.id
left join spu_sale_attr ssa on s.spu_id = ssa.spu_id and s.base_sale_attr_id = ssa.base_sale_attr_id
where ssav.spu_id = 10
GROUP BY
ssav.sku_id;
ProductApiController
/**
* 在一组SPU商品下 切换不同SKU字符串
* @param spuId "{'销售属性1|销售属性2':'sku商品ID','销售属性1|销售属性3':'sku商品ID'}"
* @return
*/
@GetMapping("/inner/getSkuValueIdsMap/{spuId}")
public String getSkuValueIdsMap(@PathVariable("spuId") Long spuId){
return skuManageService.getSkuValueIdsMap(spuId);
}
SkuManageService接口
/**
* 在一组SPU商品下 切换不同SKU字符串
* @param spuId "{'销售属性1|销售属性2':'sku商品ID','销售属性1|销售属性3':'sku商品ID'}"
* @return
*/
String getSkuValueIdsMap(Long spuId);
SpuManageServiceImpl实现类
/**
* 在一组SPU商品下 切换不同SKU字符串
* @param spuId "{'销售属性1|销售属性2':'sku商品ID','销售属性1|销售属性3':'sku商品ID'}"
* @return
*/
@Override
public String getSkuValueIdsMap(Long spuId) {
SkuSaleAttrValueMapper skuSaleAttrValueMapper = (SkuSaleAttrValueMapper) skuSaleAttrValueService.getBaseMapper();
//1.调用动态SQL得到多条 销售属性跟SKUID对照关系
List<Map> list = skuSaleAttrValueMapper.getSkuValueIdsMap(spuId);
//2.遍历List将所有销售属性ID作为jsonKey 商品SKUID作为val
HashMap<Object, Object> mapResult = new HashMap<>();
for (Map map : list) {
Object value_ids = map.get("value_ids");
Object skuId = map.get("sku_id");
mapResult.put(value_ids, skuId);
}
return JSON.toJSONString(mapResult);
}
package com.atguigu.gmall.product.mapper;
import com.atguigu.gmall.product.model.SkuSaleAttrValue;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
/**
* sku销售属性值 Mapper 接口
*
* @author atguigu
* @since 2023-06-09
*/
public interface SkuSaleAttrValueMapper extends BaseMapper<SkuSaleAttrValue> {
/**
* 在一组SPU商品下 切换不同SKU字符串
* @param spuId "{'销售属性1|销售属性2':'sku商品ID','销售属性1|销售属性3':'sku商品ID'}"
* @return
*/
List<Map> getSkuValueIdsMap(@Param("spuId") Long spuId);
}
SkuSaleAttrValueMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.gmall.product.mapper.SkuSaleAttrValueMapper">
<select id="getSkuValueIdsMap" resultType="java.util.Map">
select
group_concat(ssav.sale_attr_value_id order by sa.id,sp.id separator '|') value_ids,
ssav.sku_id
from sku_sale_attr_value ssav
left join spu_sale_attr_value sp on sp.id= ssav.sale_attr_value_id
left join spu_sale_attr sa on sa.spu_id = sp.spu_id and sa.base_sale_attr_id = sp.base_sale_attr_id
where ssav.spu_id = #{spuId}
group by ssav.sku_id
</select>
</mapper>
说明:目前我们在service-product里面把数据模型已经封装好了,接下封装feign client api接口,提供给service-item微服务调用汇总数据模型
该模块管理所有微服务远程调用的feign client api模块。搭建方式如:gmall-common父模块
gmall-parent
新增子模块提供pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>gmall-parent</artifactId>
<groupId>com.atguigu.gmall</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>gmall-client</artifactId>
<packaging>pom</packaging>
<dependencies>
<dependency>
<groupId>com.atguigu.gmall</groupId>
<artifactId>common-util</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.atguigu.gmall</groupId>
<artifactId>gmall-model</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 服务调用feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<scope>provided </scope>
</dependency>
</dependencies>
</project>
在gmall-client
模块下创建:service-product-client
在service-product-client
模块新建包名:com.atguigu.gmall.product.client 包下创建Feign接口
package com.atguigu.gmall.product.client;
import com.atguigu.gmall.product.client.impl.ProductDegradeFeignClient;
import com.atguigu.gmall.product.model.*;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
@FeignClient(value = "service-product", fallback = ProductDegradeFeignClient.class) // http://service-product
public interface ProductFeignClient {
/**
* 获取商品信息 包含商品图片
*
* @param skuId
* @return
*/
@GetMapping("/api/product/inner/getSkuInfo/{skuId}")
public SkuInfo getSkuInfo(@PathVariable("skuId") Long skuId);
/**
* 根据三级分类ID 查询所属所有三级分类信息
*
* @param category3Id
* @return
*/
@GetMapping("/api/product/inner/getCategoryView/{category3Id}")
public BaseCategoryView getCategoryView(@PathVariable("category3Id") Long category3Id);
/**
* 根据skuID查询商品价格
*
* @param skuId
* @return
*/
@GetMapping("/api/product/inner/getSkuPrice/{skuId}")
public BigDecimal getSkuPrice(@PathVariable("skuId") Long skuId);
/**
* 根据SPUID查询商品所有海报列表
* @param spuId
* @return
*/
@GetMapping("/api/product/inner/findSpuPosterBySpuId/{spuId}")
public List<SpuPoster> getSpuPosterBySpuId(@PathVariable("spuId") Long spuId);
/**
* 根据商品SkuID查询当前sku商品所有平台属性名称包含属性值
*
* @param skuId
* @return
*/
@GetMapping("/api/product/inner/getAttrList/{skuId}")
public List<BaseAttrInfo> getAttrList(@PathVariable("skuId") Long skuId);
/**
* 查询SPU所有销售属性,将指定传入SKU商品销售属性区分是否选中
*
* @param skuId
* @param spuId
* @return
*/
@GetMapping("/api/product/inner/getSpuSaleAttrListCheckBySku/{skuId}/{spuId}")
public List<SpuSaleAttr> getSpuSaleAttrListCheckBySku(
@PathVariable("skuId") Long skuId,
@PathVariable("spuId") Long spuId);
/**
* 查询每个商品SKU 销售属性 跟 SKUID 对照关系
*
* @param spuId
* @return {"3732|3734":24,"3732|3735":25}
*/
@GetMapping("/api/product/inner/getSkuValueIdsMap/{spuId}")
public Map getSkuValueIdsMap(@PathVariable("spuId") Long spuId);
}
为了保护上游服务,进一步提供服务降级类
package com.atguigu.gmall.product.client;
import com.atguigu.gmall.product.client.impl.ProductDegradeFeignClient;
import com.atguigu.gmall.product.model.*;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
@FeignClient(value = "service-product", fallback = ProductDegradeFeignClient.class) // http://service-product
public interface ProductFeignClient {
/**
* 获取商品信息 包含商品图片
*
* @param skuId
* @return
*/
@GetMapping("/api/product/inner/getSkuInfo/{skuId}")
public SkuInfo getSkuInfo(@PathVariable("skuId") Long skuId);
/**
* 根据三级分类ID 查询所属所有三级分类信息
*
* @param category3Id
* @return
*/
@GetMapping("/api/product/inner/getCategoryView/{category3Id}")
public BaseCategoryView getCategoryView(@PathVariable("category3Id") Long category3Id);
/**
* 根据skuID查询商品价格
*
* @param skuId
* @return
*/
@GetMapping("/api/product/inner/getSkuPrice/{skuId}")
public BigDecimal getSkuPrice(@PathVariable("skuId") Long skuId);
/**
* 根据SPUID查询商品所有海报列表
* @param spuId
* @return
*/
@GetMapping("/api/product/inner/findSpuPosterBySpuId/{spuId}")
public List<SpuPoster> getSpuPosterBySpuId(@PathVariable("spuId") Long spuId);
/**
* 根据商品SkuID查询当前sku商品所有平台属性名称包含属性值
*
* @param skuId
* @return
*/
@GetMapping("/api/product/inner/getAttrList/{skuId}")
public List<BaseAttrInfo> getAttrList(@PathVariable("skuId") Long skuId);
/**
* 查询SPU所有销售属性,将指定传入SKU商品销售属性区分是否选中
*
* @param skuId
* @param spuId
* @return
*/
@GetMapping("/api/product/inner/getSpuSaleAttrListCheckBySku/{skuId}/{spuId}")
public List<SpuSaleAttr> getSpuSaleAttrListCheckBySku(
@PathVariable("skuId") Long skuId,
@PathVariable("spuId") Long spuId);
/**
* 查询每个商品SKU 销售属性 跟 SKUID 对照关系
*
* @param spuId
* @return {"3732|3734":24,"3732|3735":25}
*/
@GetMapping("/api/product/inner/getSkuValueIdsMap/{spuId}")
public Map getSkuValueIdsMap(@PathVariable("spuId") Long spuId);
}
说明:接下来service-item引用service-product-client模块,就可以调用相应接口
点击gmall-service
,新建子模块:service-item 。选择New–>Module,操作如下
下一步
下一步
完成,结构如下
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>gmall-service</artifactId>
<groupId>com.atguigu.gmall</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>service-item</artifactId>
</project>
添加配置文件:bootstrap.properties
spring.application.name=service-item
spring.profiles.active=dev
spring.cloud.nacos.discovery.server-addr=192.168.200.128:8848
spring.cloud.nacos.config.server-addr=192.168.200.128:8848
spring.cloud.nacos.config.prefix=${spring.application.name}
spring.cloud.nacos.config.file-extension=yaml
spring.cloud.nacos.config.shared-configs[0].data-id=common.yaml
添加启动类
exclude = DataSourceAutoConfiguration.class 排除数据库链接jar表示当前项目{service-item} 不参与数据库查询
package com.atguigu.gmall;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消数据源自动配置
@EnableFeignClients
public class ItemApp {
public static void main(String[] args) {
SpringApplication.run(ItemApp.class, args);
}
}
YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/699
在service-item
模块pom.xml中引入service-product-client
依赖
<dependencies>
<dependency>
<groupId>com.atguigu.gmall</groupId>
<artifactId>service-product-client</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
提供获取商品详情相关数据RestFul接口实现
package com.atguigu.gmall.item.controller;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.item.service.ItemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* @author: atguigu
* @create: 2023-04-22 09:10
*/
@RestController
@RequestMapping("/api/item")
public class ItemController {
@Autowired
private ItemService itemService;
/**
* 查询商品信息,汇总详情页面需要数据
*
* @param skuId
* @return
*/
@GetMapping("/{skuId}")
public Result getItemInfo(@PathVariable("skuId") Long skuId) {
Map<String, Object> itemData = itemService.getItemInfo(skuId);
return Result.ok(itemData);
}
}
业务层完成数据汇总
package com.atguigu.gmall.item.service.impl;
import com.atguigu.gmall.item.service.ItemService;
import com.atguigu.gmall.product.client.ProductFeignClient;
import com.atguigu.gmall.product.model.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author: atguigu
* @create: 2023-06-10 16:28
*/
@Service
@SuppressWarnings("all")
public class ItemServiceImpl implements ItemService {
@Autowired
private ProductFeignClient productFeignClient;
/**
* 远程调用商品微服务调用七项数据feign接口完成数据汇总
*
* @param skuId
* @return
*/
@Override
public Map<String, Object> getItemInfo(Long skuId) {
HashMap<String, Object> mapResult = new HashMap<>();
//1.根据SkuID查询Sku商品信息
SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
mapResult.put("skuInfo", skuInfo);
//2.根据商品所属三级分类ID查询分类信息
BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
mapResult.put("categoryView", categoryView);
//3.根据SKuID查询Sku商品价格
BigDecimal skuPrice = productFeignClient.getSkuPrice(skuId);
mapResult.put("price", skuPrice);
//4.根据商品SpuID查询海报图片列表
List<SpuPoster> spuPosterList = productFeignClient.getSpuPosterBySpuId(skuInfo.getSpuId());
mapResult.put("spuPosterList", spuPosterList);
//5.根据skuId查询当前SKU商品包含平台属性列表(包含属性值)
List<BaseAttrInfo> attrList = productFeignClient.getAttrList(skuId);
mapResult.put("skuAttrList", attrList);
//6.查询SPU商品所有销售属性,查询指定SKU销售属性选中效果
List<SpuSaleAttr> spuSaleAttrList = productFeignClient.getSpuSaleAttrListCheckBySku(skuId, skuInfo.getSpuId());
mapResult.put("spuSaleAttrList", spuSaleAttrList);
//7.在一组SPU商品下 切换不同SKU字符串
String skuValueIdsMap = productFeignClient.getSkuValueIdsMap(skuInfo.getSpuId());
mapResult.put("valuesSkuJson", skuValueIdsMap);
return mapResult;
}
}
在gmall-client
父工程下创建:service-item-client 子模块。搭建方式同service-product-client
提供远程调用Feign API接口
package com.atguigu.gmall.item.client;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.item.client.impl.ItemDegradeFeignClient;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.Map;
/**
* @author: atguigu
* @create: 2023-04-22 09:29
*/
@FeignClient(value = "service-item", fallback = ItemDegradeFeignClient.class)
public interface ItemFeignClient {
/**
* 查询商品信息,汇总详情页面需要数据
*
* @param skuId
* @return
*/
@GetMapping("/api/item/{skuId}")
public Result<Map> getItemInfo(@PathVariable("skuId") Long skuId);
}
服务降级类
package com.atguigu.gmall.item.client.impl;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.item.client.ItemFeignClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @author: atguigu
* @create: 2023-04-22 09:29
*/
@Slf4j
@Component
public class ItemDegradeFeignClient implements ItemFeignClient {
@Override
public Result getItemInfo(Long skuId) {
log.error("[详情服务]执行了服务降级方法");
return null;
}
}
选中gmall-common
父工程新建子模块:web-util,搭建方式如service-util
配置:pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>gmall-common</artifactId>
<groupId>com.atguigu.gmall</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>web-util</artifactId>
<dependencies>
<dependency>
<groupId>com.atguigu.gmall</groupId>
<artifactId>common-util</artifactId>
<version>1.0</version>
</dependency>
<!-- 服务调用feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided </scope>
</dependency>
</dependencies>
</project>
导入工具类,将目录中工具类复制到web-util
模块中:
gmall-parent
父工程新建子模块:gmall-web
修改配置pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>gmall-parent</artifactId>
<groupId>com.atguigu.gmall</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>gmall-web</artifactId>
<dependencies>
<dependency>
<groupId>com.atguigu.gmall</groupId>
<artifactId>web-util</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- 服务注册 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 服务配置-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- 服务调用feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 流量控制 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--导入详情微服务feign模块-->
<dependency>
<groupId>com.atguigu.gmall</groupId>
<artifactId>service-item-client</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
启动类
package com.atguigu.gmall;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消数据源自动配置
@EnableFeignClients
public class WebAll {
public static void main(String[] args) {
SpringApplication.run(WebAll.class, args);
}
}
在 gmall-web
模块中resources
目录下新建:bootstrap.properties 文件
spring.application.name=web-all
spring.profiles.active=dev
spring.cloud.nacos.discovery.server-addr=192.168.200.128:8848
spring.cloud.nacos.config.server-addr=192.168.200.128:8848
spring.cloud.nacos.config.prefix=${spring.application.name}
spring.cloud.nacos.config.file-extension=yaml
spring.cloud.nacos.config.shared-configs[0].data-id=common.yaml
在配套资料中02 工具类
中gmall-web 文件夹下 有static,templates ,将这两个文件夹放入到resouces
文件夹中。
导入之后,可能发生异常警告
解决方案:将JS规范版本设置为ECMAScript6版本
在gmall-web
的控制器中远程调用商品详情微服务(service-item)中汇总商品SKU方法
package com.atguigu.gmall.web.controller;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.item.client.ItemFeignClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.Map;
/**
* @author: atguigu
* @create: 2023-06-10 16:46
*/
@Controller
public class ItemHtmlController {
@Autowired
private ItemFeignClient itemFeignClient;
/**
* 渲染商品详情页
*
* @param skuId
* @return
*/
@GetMapping("/{skuId}.html")
public String itemHtml(Model model, @PathVariable("skuId") Long skuId) {
//1.远程调用详情服务获取所有数据
Result<Map> result = itemFeignClient.getItemInfo(skuId);
//2.将获取到数据封装到Model中,Thymeleaf底层使用模板引擎将数据跟模板页面进行合并 生成静态页
model.addAllAttributes(result.getData());
return "/item/item";
}
}
浏览器中直接方法前端gmall-web微服务测试:
通过网关域名映射访问测试: