- 谷粒商城-分布式基础篇【环境准备】
- 谷粒商城-分布式基础【业务编写】
- 谷粒商城-分布式高级篇【业务编写】持续更新
- 谷粒商城-分布式高级篇-ElasticSearch
- 谷粒商城-分布式高级篇-分布式锁与缓存
- 项目托管于gitee
一、搭建页面环境
1.1.1、配置 Nginx 和 网关
配置 Nginx 和 网关
-
修改本地的 hosts文件
vim /etc/hosts
# Gulimall Host Start 127.0.0.1 gulimall.cn 127.0.0.1 search.gulimall.cn 127.0.0.1 item.gulimall.cn # Gulimall Host End
-
配置Nginx(将search下的请求转给网关)
商城业务-检索服务的时候已经配置过了,这里不需要修改 -
配置网关
修改gulimall-gateway服务/src/main/resources
路径下的 application.yml- id: gulimall_host_route uri: lb://gulimall-product predicates: - Host=gulimall.cn,item.gulimall.cn
1.1.2、动静资源配置
动静分离
-
将
2.分布式高级篇/代码/html/详情页
路径下的 shangpinxiangqing.html 复制到 gulimall-product服务src/main/resources/templates
路径下 并修改名字为 item.html -
将
2.分布式高级篇/代码/html/详情页
路径下的 所有其他静态资源复制到 nginx 的hgw@HGWdeAir item % pwd /Users/hgw/Documents/Software/mydata/nginx/html/static/item hgw@HGWdeAir item % ll total 0 drwxrwxr-x@ 5 hgw staff 160B 5 29 2019 bootstrap drwxrwxr-x@ 13 hgw staff 416B 3 22 2020 image drwxrwxr-x@ 284 hgw staff 8.9K 3 22 2020 img drwxrwxr-x@ 6 hgw staff 192B 3 22 2020 js drwxrwxr-x@ 6 hgw staff 192B 3 22 2020 scss
-
修改index.html 的静态资源请求路径
href=" --> href="/static/item/ src=" --> src="/static/item/
1.1.3、调整页面跳转
-
修改gulimall-search服务中
list.html
文件<p class="da"> <a th:href="|http://item.gulimall.cn/${product.skuId}.html|" > <img th:src="${product.skuImg}" class="dim"> </a> </p>
-
编写 Controller 实现页面跳转
-
添加“com.atguigu.gulimall.product.web.ItemController”类,代码如下:
@Controller public class ItemController { /** * 展示当前sku的详情 * @param skuId * @return */ @GetMapping("/{skuId}.html") public String skuItem(@PathVariable("skuId") Long skuId) { System.out.println("准备查询:" + skuId + "的详情"); return "item.html"; } }
-
访问测试:
二、模型抽取
因此建立以下vo
@Data
public class SkuItemVo {
// 1、sku基本信息 pms_sku_info
SkuInfoEntity info;
// 2、sku的图片信息 pms_sku_images
List<SkuImagesEntity> images;
// 3、获取 spu 的销售属性组合
List<SkuItemSaleAttrsVo> saleAttr;
// 4、获取 spu 的介绍 pms_spu_info_desc
SpuInfoDescEntity desp;
// 5、获取 spu 的规格参数信息
List<SpuItemAttrGroupVo> groupAttrs;
// 销售属性组合
@Data
public static class SkuItemSaleAttrsVo {
private Long attrId;
private String attrName;
private String attrValues;
}
// 规格参数
@Data
public static class SpuItemAttrGroupVo{
private String groupName;
private List<SpuBaseAttrsVo> attrs;
}
// 基本属性
@Data
public static class SpuBaseAttrsVo {
private String attrName;
private String attrValues;
}
}
后因为联表查询原因将内部类提取出来:
@Data
public class SkuItemVo {
// 1、sku基本信息 pms_sku_info
SkuInfoEntity info;
// 2、sku的图片信息 pms_sku_images
List<SkuImagesEntity> images;
// 3、获取 spu 的销售属性组合
List<SkuItemSaleAttrsVo> saleAttr;
// 4、获取 spu 的介绍 pms_spu_info_desc
SpuInfoDescEntity desp;
// 5、获取 spu 的规格参数信息
List<SpuItemAttrGroupVo> groupAttrs;
}
销售属性组合:
@Data
public class SkuItemSaleAttrsVo {
private Long attrId;
private String attrName;
private String attrValues;
}
规格参数 :
@ToString
@Data
public class SpuItemAttrGroupVo {
private String groupName;
private List<Attr> attrs;
}
@Data
public class Attr {
private Long attrId;
private String attrName;
private String attrValue;
}
三、封装商品
Controller 层编写:
@Controller
public class ItemController {
@Autowired
SkuInfoService skuInfoService;
/**
* 展示当前sku的详情
* @param skuId
* @return
*/
@GetMapping("/{skuId}.html")
public String skuItem(@PathVariable("skuId") Long skuId, Model model) {
SkuItemVo skuItemVo = skuInfoService.item(skuId);
model.addAttribute("item",skuItemVo);
return "item.html";
}
}
Service 实现类方法编写:
3.1、sku 基本信息获取
查询 pms_sku_info
// 1、sku基本信息 pms_sku_info
SkuInfoEntity info = getById(skuId);
skuItemVo.setInfo(info);
3.2、获取spu的图片信息
查询 pms_sku_images
@Autowired
SkuImagesService imagesService;
// 2、sku的图片信息 pms_sku_images
List<SkuImagesEntity> images = imagesService.getImagesBySkuId(skuId);
skuItemVo.setImages(images);
注入 SkuImagesService,调用该实现类的 getImagesBySkuId(skuId)
方法获取spu的图片信息
com.atguigu.gulimall.product.service.impl
路径下的 SkuImagesServiceImpl 实现类编写:
@Override
public List<SkuImagesEntity> getImagesBySkuId(Long skuId) {
SkuImagesDao imagesDao = this.baseMapper;
List<SkuImagesEntity> imagesEntities = imagesDao.selectList(new QueryWrapper<SkuImagesEntity>().eq("sku_id", skuId));
return imagesEntities;
}
3.3、获取spu的销售属性组合
获取spu的销售属性组合
@Autowired
SkuSaleAttrValueService saleAttrValueService;
// 3、获取 spu 的销售属性组合
List<SkuItemSaleAttrsVo> saleAttrVos = saleAttrValueService.getSaleAttrsBySpuId(spuId);
skuItemVo.setSaleAttr(saleAttrVos);
注入 SkuSaleAttrValueService,调用该实现类的getSaleAttrsBySpuId(spuId)
方法
@Override
public List<SkuItemSaleAttrsVo> getSaleAttrsBySpuId(Long spuId) {
SkuSaleAttrValueDao dao = this.baseMapper;
List<SkuItemSaleAttrsVo> saleAttrVos = dao.getSaleAttrsBySpuId(spuId);
return saleAttrVos;
}
使用SkuSaleAttrValueDao 层 getSaleAttrsBySpuId 方法:
package com.atguigu.gulimall.product.dao;
@Mapper
public interface SkuSaleAttrValueDao extends BaseMapper<SkuSaleAttrValueEntity> {
List<SkuItemSaleAttrsVo> getSaleAttrsBySpuId(@Param("spuId") Long spuId);
}
gulimall-product/src/main/resources/mapper/product/
SkuSaleAttrValueDao.xml
<select id="getSaleAttrsBySpuId" resultType="com.atguigu.gulimall.product.vo.SkuItemSaleAttrsVo">
SELECT
ssav.attr_id attr_id,
ssav.attr_name attr_name,
GROUP_CONCAT(DISTINCT ssav.attr_value) attr_values
FROM pms_sku_info info
LEFT JOIN pms_sku_sale_attr_value ssav ON ssav.sku_id = info.sku_id
WHERE spu_id = #{spuId}
GROUP BY ssav.attr_id,ssav.attr_name;
</select>
分析当前spu有多少了sku,所有sku涉及到的属性组合
- 通过spu_id 查询
pms_sku_info
表,获得当前spu对应的 sku_id - 通过sku_id 查询
pms_sku_sale_attr_value
表,获取 当前spu 对应的所有的sku的销售属性 - 通过汇总函数封装成我们想要的样子
3.4、获取 spu 的介绍
查询 pms_spu_info_desc
@Autowired
SpuInfoDescService spuInfoDescService;
// 4、获取 spu 的介绍 pms_spu_info_desc
Long spuId = info.getSpuId();
SpuInfoDescEntity spuInfoDescEntity = spuInfoDescService.getById(spuId);
skuItemVo.setDesp(spuInfoDescEntity);
3.5、获取 spu 的规格参数信息
查询 pms_spu_info_desc
@Autowired
AttrGroupService attrGroupService;
Long spuId = info.getSpuId();
Long catalogId = info.getCatalogId();
// 5、获取 spu 的规格参数信息 pms_spu_info_desc
List<SpuItemAttrGroupVo> attrGroupVos = attrGroupService.getAttrGroupWithAttrsBySpuId(spuId,catalogId);
skuItemVo.setGroupAttrs(attrGroupVos);
注入 AttrGroupService,调用该实现类的 getAttrGroupWithAttrsBySpuId(spuId,catalogId)
方法
/**
* 查处当前spuId对应的所有属性分组信息 以及 当前分组下的所有属性对应的值
* @param spuId
* @param catalogId
* @return
*/
@Override
public List<SpuItemAttrGroupVo> getAttrGroupWithAttrsBySpuId(Long spuId, Long catalogId) {
AttrGroupDao baseMapper = this.getBaseMapper();
List<SpuItemAttrGroupVo> vos = baseMapper.getAttrGroupWithAttrsBySpuId(spuId,catalogId);
return vos;
}
使用AttrGroupDao 层 getAttrGroupWithAttrsBySpuId 方法:
package com.atguigu.gulimall.product.dao;
@Mapper
public interface AttrGroupDao extends BaseMapper<AttrGroupEntity> {
List<SpuItemAttrGroupVo> getAttrGroupWithAttrsBySpuId(@Param("spuId") Long spuId, @Param("catalogId") Long catalogId);
}
gulimall-product/src/main/resources/mapper/product/AttrGroupDao.xml :
<!--resultType 返回集合里面元素的类型,只要有嵌套属性就要自定义结果集-->
<resultMap id="spuItemAttrGroupVo" type="com.atguigu.gulimall.product.vo.SpuItemAttrGroupVo">
<result property="groupName" column="attr_group_name"></result>
<collection property="attrs" ofType="com.atguigu.gulimall.product.vo.Attr">
<result property="attrName" column="attr_name"></result>
<result property="attrValue" column="attr_value"></result>
</collection>
</resultMap>
<select id="getAttrGroupWithAttrsBySpuId" resultMap="spuItemAttrGroupVo">
SELECT
pav.spu_id,
ag.attr_group_name,
ag.attr_group_id,
aar.attr_id,
attr.attr_name,
pav.attr_value
FROM pms_attr_group ag LEFT JOIN pms_attr_attrgroup_relation aar ON ag.attr_group_id = aar.attr_group_id
LEFT JOIN pms_attr attr ON attr.attr_id = aar.attr_id
LEFT JOIN pms_product_attr_value pav on pav.attr_id = attr.attr_id
WHERE ag.catelog_id=#{catalogId} AND pav.spu_id = #{spuId};
</select>
这里使用了联表查询:
- 通过
catelog_id
查询 pms_attr_group 表中对应的 属性分组的信息attr_group_id
、attr_group_name
- 通过
attr_group_id
联表查询 pms_attr_attrgroup_relation 表中的属性idattr_id
- 通过
attr_id
联表查询 pms_attr 表中对应的attr_name
、attr_id
- 通过
attr_id
联表查询 pms_product_attr_value 表中对应的 属性值attr_value
四、详情页渲染
4.1、基本渲染
1、添加thymeleaf的名称空间
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
2、标题名设置
<div class="box-name" th:text="${item.info.skuTitle}">
华为 HUAWEI Mate 10 6GB+128GB 亮黑色 移动联通电信4G手机 双卡双待
</div>
<div class="box-hide" th:text="${item.info.skuSubtitle}">预订用户预计11月30日左右陆续发货!麒麟970芯片!AI智能拍照!
<a href="/static/item/"><u></u></a>
</div>
3、大图显示
<div class="imgbox">
<div class="probox">
<img class="img1" alt="" th:src="${item.info.skuDefaultImg}">
<div class="hoverbox"></div>
</div>
<div class="showbox">
<img class="img1" alt="" th:src="${item.info.skuDefaultImg}">
</div>
</div>
4、价格设置
<div class="box-summary clear">
<ul>
<li>京东价</li>
<li>
<span>¥</span>
<span th:text="${#numbers.formatDecimal(item.info.price,0,2)}">4499.00</span>
</li>
<li>
预约享资格
</li>
<li>
<a href="/static/item/">
预约说明
</a>
</li>
</ul>
</div>
5、是否有货
<li th:text="${item.hasStock?'有货':'无货'}">
<span>无货</span>, 此商品暂时售完
</li>
6、小图显示
<div class="box-lh-one">
<ul>
<li th:each="img:${item.images}" th:if="${!#strings.isEmpty(img.imgUrl)}"><img th:src="${img.imgUrl}" /></li>
</ul>
</div>
7、销售属性
<div class="box-attr-3">
<div class="box-attr-2 clear" th:each="attr:${item.saleAttr}">
<dl>
<dt>选择[[${attr.attrName}]]</dt>
<dd th:each="val:${#strings.listSplit(attr.attrValues,',')}">
[[${val}]]
</dd>
</dl>
</div>
</div>
8、商品介绍
<img class="xiaoguo" th:src="${desccp}" th:each="desccp:${#strings.listSplit(item.desp.decript,',')}" />
9、规格包装
<li class="baozhuang actives" id="li2">
<div class="guiGebox" >
<div class="guiGe" th:each="group:${item.groupAttrs}">
<h3 th:text="${group.groupName}">主体</h3>
<dl>
<div th:each="attr:${group.attrs}">
<dt th:text="${attr.attrName}">品牌</dt>
<dd th:text="${attr.attrValue}">华为(HUAWEI)</dd>
</div>
</dl>
</div>
<div class="package-list">
<h3>包装清单</h3>
<p>手机(含内置电池) X 1、5A大电流华为SuperCharge充电器X 1、5A USB数据线 X 1、半入耳式线控耳机 X 1、快速指南X 1、三包凭证 X 1、取卡针 X 1、保护壳 X 1</p>
</div>
</div>
</li>
五、sku组合切换
需求:通过不同的销售属性渲染sku商品
通过选择销售属性获取该销售属性对应的sku,通过算法选中该sku
1、封装Vo类
-
在
com.atguigu.gulimall.product.vo
路径下创建 AttrValueWithSkuIdVo 类@Data public class AttrValueWithSkuIdVo { private String attrValue; private String skuIds; }
-
修改 SkuItemSaleAttrsVo 类
@Data public class SkuItemSaleAttrsVo { private Long attrId; private String attrName; private List<AttrValueWithSkuIdVo> attrValues; }
2、修改Dao层mapper
修改gulimall-product/src/main/resources/mapper/product/SkuSaleAttrValueDao.xml
<resultMap id="skuItemSaleAttrsVo" type="com.atguigu.gulimall.product.vo.SkuItemSaleAttrsVo">
<result property="attrId" column="attr_id"/>
<result property="attrName" column="attr_name"/>
<collection property="attrValues" ofType="com.atguigu.gulimall.product.vo.AttrValueWithSkuIdVo">
<result property="attrValue" column="attr_value"/>
<result property="skuIds" column="sku_ids"/>
</collection>
</resultMap>
<select id="getSaleAttrsBySpuId" resultMap="skuItemSaleAttrsVo">
SELECT
ssav.attr_id attr_id,
ssav.attr_name attr_name,
ssav.attr_value attr_value,
GROUP_CONCAT(DISTINCT info.sku_id) sku_ids
FROM pms_sku_info info
LEFT JOIN pms_sku_sale_attr_value ssav ON ssav.sku_id = info.sku_id
WHERE info.spu_id = #{spuId}
GROUP BY ssav.attr_id,ssav.attr_name,ssav.attr_value;
</select>
3、 修改item.html文件,重新渲染销售属性
<div class="box-attr-3">
<div class="box-attr-2 clear" th:each="attr:${item.saleAttr}">
<dl>
<dt>选择[[${attr.attrName}]]</dt>
<dd th:each="vals:${attr.attrValues}">
<a
th:attr="
class=${#lists.contains(#strings.listSplit(vals.skuIds,','),item.info.skuId.toString())}?'sku_attr_value checked':'sku_attr_value',
skus=${vals.skuIds}">
[[${vals.attrValue}]]
</a>
</dd>
</dl>
</div>
</div>
<script>
$(".sku_attr_value").click(function () {
// 1、点击的元素添加自定义的属性。为了识别我们是刚才被点击
var skus = new Array();
$(this).addClass("ckicked");
// 寻找本列属性中class属性中有 ckicked
var curr = $(this).attr("skus").split(",");
// 将当前被点击的所有sku组合数组放进去
skus.push(curr);
// 去掉同一行中所有的 checked
$(this).parent().parent().find(".sku_attr_value").removeClass("checked");
// 寻找其他属性中class属性有 checked
$("a[class='sku_attr_value checked']").each(function () {
skus.push($(this).attr("skus").split(","));
});
console.log(skus);
// 2、取出他们的交集,得到skuId
var filterEle = skus[0];
for (var i = 1; i<skus.length; i++) {
filterEle = $(filterEle).filter(skus[i]);
}
console.log(filterEle[0]);
// 3、跳转
location.href = "http://item.gulimall.cn/"+ filterEle[0] +".html";
});
$(function () {
$(".sku_attr_value").parent().css({"border":"solid 1px #CCC"});
$("a[class='sku_attr_value checked']").parent().css({"border":"solid 1px red"});
})
</script>
六、异步编排优化
第一步、编写创建线程池工具类
1、添加线程池属性配置类,并注入到容器中
package com.atguigu.gulimall.product.config;
@ConfigurationProperties(prefix = "gulimall.thread")
@Component
@Data
public class ThreadPoolConfigProperties {
private Integer coreSize;
private Integer maxSize;
private Integer keepAliveTime;
}
这里提示需要导入一个工具依赖,不导也可以:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
2、添加线程池属性的配置
在gulimall-product服务中加入以下配置:
# 配置线程池
gulimall:
thread:
core-size: 20
max-size: 200
keep-alive-time: 10
3、线程池配置,获取线程池的属性值这里直接调用与配置文件相对应的属性配置类
package com.atguigu.gulimall.product.config;
@Configuration
public class MyThreadConfig {
@Bean
public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties pool) {
return new ThreadPoolExecutor(pool.getCoreSize(),
pool.getMaxSize(),
pool.getKeepAliveTime(),
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(100000),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
}
}
第二步、异步编排优化
@Autowired
ThreadPoolExecutor executor;
-
infoFuture
- saleAttrFuture
- descFuture
- baseAttrFuture (这三个异步任务需要 infoFuture 执行完得到其结果才能执行)
-
imageFuture
参考异步笔记:异步笔记
@Override
public SkuItemVo item(Long skuId) {
SkuItemVo skuItemVo = new SkuItemVo();
// 1、sku基本信息 pms_sku_info
CompletableFuture<SkuInfoEntity> infoFuture = CompletableFuture.supplyAsync(() -> {
SkuInfoEntity info = getById(skuId);
skuItemVo.setInfo(info);
return info;
}, executor);
// 2、获取 spu 的销售属性组合
CompletableFuture<Void> saleAttrFuture = infoFuture.thenAcceptAsync(res -> {
List<SkuItemSaleAttrsVo> saleAttrVos = saleAttrValueService.getSaleAttrsBySpuId(res.getSpuId());
skuItemVo.setSaleAttr(saleAttrVos);
}, executor);
// 3、获取 spu 的介绍 pms_spu_info_desc
CompletableFuture<Void> descFuture = infoFuture.thenAcceptAsync(res -> {
SpuInfoDescEntity spuInfoDescEntity = spuInfoDescService.getById(res.getSpuId());
skuItemVo.setDesp(spuInfoDescEntity);
}, executor);
// 4、获取 spu 的规格参数信息 pms_spu_info_desc
CompletableFuture<Void> baseAttrFuture = infoFuture.thenAcceptAsync(res -> {
List<SpuItemAttrGroupVo> attrGroupVos = attrGroupService.getAttrGroupWithAttrsBySpuId(res.getSpuId(), res.getCatalogId());
skuItemVo.setGroupAttrs(attrGroupVos);
}, executor);
// 5、sku的图片信息 pms_sku_images
CompletableFuture<Void> imageFuture = CompletableFuture.runAsync(() -> {
List<SkuImagesEntity> images = imagesService.getImagesBySkuId(skuId);
skuItemVo.setImages(images);
}, executor);
// 等待所有任务都完成
CompletableFuture.allOf(saleAttrFuture,descFuture,baseAttrFuture,imageFuture).join();
return skuItemVo;
}