目录
1、搭建页面环境
1.1、配置 Nginx 和 网关
1.2、动静资源配置
1.3、搜索页到详情页跳转
2、模型类抽取和controller
2.1、分析首页需要展示的信息
2.2、首页模型类vo
2.3、销售属性组合
2.4、规格参数
2.5、创建ItemController,展示当前sku的详情
3.0、业务流程,根据sku_id获取首页信息
3.1、sku 基本信息获取
3.2、获取sku的图片信息
3.3、获取spu的销售属性组合
3.3.1、SkuInfoServiceImpl
3.3.2、SkuSaleAttrValueServiceImpl
3.3.3、dao
3.3.4、sql,查询指定spu_id下的所有销售属性id,name,value
3.3.5、mapper
3.4、获取 spu 的介绍
3.5、获取 spu 的规格参数信息
4、前端,详情页渲染
5、sku组合切换,点击销售属性跳转sku商品
5.1、封装Vo类
5.2、mapper,通过sku_id获取销售属性
5.3、 前端,修改item.html文件,重新渲染销售属性
6、异步编排优化
6.1、环境准备
6.1.1、添加线程池属性类
6.1.2、导入依赖,spring元数据处理器
6.1.3、yml配置线程池
6.1.4、自定义线程池配置类
6.2、异步编排优化详情页查询业务
配置 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下的请求转给网关)
商城业务-检索服务的时候已经配置过了,这里不需要修改
不确定可以再查看一下:
vim /mydata/nginx/conf/conf.d/gulimall.conf
server_name gulimall.com *.gulimall.com
配置网关
修改gulimall-gateway服务 /src/main/resources
路径下的 application.yml
- id: gulimall_host
uri: lb://gulimall-product
predicates:
- Host=gulimall.com,item.gulimall.com #设置也可以通过“item.gulimall.com”路由到商品模块
动态资源:
复制shangpinxiangqing.html到gulimall-product/src/main/resources/templates/目录下,并改名为item.index。
修改“item.index”文件里的src
和herf
的静态资源地址,前缀加“/static/item”
静态资源:
将静态资源上传至nginx
修改gulimall-search服务中 list.html
文件
编写 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"; //返回到item.html
}
}
访问测试:
搜索后点击商品进入详情页:
根据首页预期展示信息抽取:
注意:要分清哪些信息是spu的,哪些信息是sku的。
package com.xx.gulimall.product.vo;
@ToString
@Data
public class SkuItemVo {
//1、sku基本信息的获取 pms_sku_info
private SkuInfoEntity info;
private boolean hasStock = true;
//2、sku的图片信息 pms_sku_images
private List images;
//3、获取spu的销售属性组合
private List saleAttr;
//4、获取spu的介绍
private SpuInfoDescEntity desc;
//5、获取spu的规格参数信息
private List groupAttrs;
//6、秒杀商品的优惠信息
private SeckillSkuVo seckillSkuVo;
}
@Data
public class SkuItemSaleAttrsVo {
private Long attrId;
private String attrName;
private String attrValues;
}
@ToString
@Data
public class SpuItemAttrGroupVo {
private String groupName;
private List attrs;
}
@Data
public class Attr {
private Long attrId;
private String attrName;
private String attrValue;
}
package com.xxx.gulimall.product.web;
@Controller
public class ItemController {
@Resource
private SkuInfoService skuInfoService;
/**
* 展示当前sku的详情
*/
@GetMapping("/{skuId}.html")
public String skuItem(@PathVariable("skuId") Long skuId, Model model) throws ExecutionException, InterruptedException {
System.out.println("准备查询" + skuId + "详情");
SkuItemVo vos = skuInfoService.item(skuId);
model.addAttribute("item",vos);
return "item";
}
}
3、业务实现(不使用异步)
查询 pms_sku_info
com.xxx.gulimall.product.service.impl.SkuInfoServiceImpl
@Override
public SkuItemVo item(Long skuId) throws ExecutionException, InterruptedException{
// 1、sku基本信息 pms_sku_info
SkuInfoEntity info = getById(skuId);
Long spuId=info.getSpuId();
skuItemVo.setInfo(info);
}
表pms_sku_images
com.xxx.gulimall.product.service.impl.SkuInfoServiceImpl
// 2、sku的图片信息 pms_sku_images
List images = skuImagesService.getImagesBySkuId(skuId);
skuItemVo.setImages(images);
注入 SkuImagesService,调用该实现类的 getImagesBySkuId(skuId)
方法获取spu的图片信息
com.atguigu.gulimall.product.service.impl
路径下的 SkuImagesServiceImpl 实现类编写:
@Override
public List getImagesBySkuId(Long skuId) {
SkuImagesDao imagesDao = this.baseMapper;
List imagesEntities = imagesDao.selectList(new QueryWrapper().eq("sku_id", skuId));
return imagesEntities;
}
com.xxx.gulimall.product.service.impl.SkuInfoServiceImpl
// 3、获取 spu 的销售属性组合
List saleAttrVos = skuSaleAttrValueService.getSaleAttrsBySpuId(spuId);
skuItemVo.setSaleAttr(saleAttrVos);
@Override
public List getSaleAttrsBySpuId(Long spuId) {
SkuSaleAttrValueDao dao = this.baseMapper;
List saleAttrVos = dao.getSaleAttrsBySpuId(spuId);
return saleAttrVos;
}
使用SkuSaleAttrValueDao 层 getSaleAttrsBySpuId 方法:
package com.atguigu.gulimall.product.dao;
@Mapper
public interface SkuSaleAttrValueDao extends BaseMapper {
List getSaleAttrsBySpuId(@Param("spuId") Long spuId);
}
sku表pms_sku_info:
sku销售属性表pms_sku_sale_attr_value:
左外连接查询:
SELECT #查销售属性的id,name,值
ssav.attr_id attr_id,
ssav.attr_name attr_name,
GROUP_CONCAT( DISTINCT ssav.attr_value ) attr_values
FROM #sku表左外连接sku销售属性表
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;
查询结果:
gulimall-product/src/main/resources/mapper/product/
SkuSaleAttrValueDao.xml
分析当前spu有多少了sku,所有sku涉及到的属性组合
pms_sku_info
表,获得当前spu对应的 sku_idpms_sku_sale_attr_value
表,获取 当前spu 对应的所有的sku的销售属性查询 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);
查询 pms_spu_info_desc
@Autowired
AttrGroupService attrGroupService;
Long spuId = info.getSpuId();
Long catalogId = info.getCatalogId();
// 5、获取 spu 的规格参数信息 pms_spu_info_desc
List attrGroupVos = attrGroupService.getAttrGroupWithAttrsBySpuId(spuId,catalogId);
skuItemVo.setGroupAttrs(attrGroupVos);
注入 AttrGroupService,调用该实现类的 getAttrGroupWithAttrsBySpuId(spuId,catalogId)
方法
/**
* 查处当前spuId对应的所有属性分组信息 以及 当前分组下的所有属性对应的值
* @param spuId
* @param catalogId
* @return
*/
@Override
public List getAttrGroupWithAttrsBySpuId(Long spuId, Long catalogId) {
AttrGroupDao baseMapper = this.getBaseMapper();
List vos = baseMapper.getAttrGroupWithAttrsBySpuId(spuId,catalogId);
return vos;
}
使用AttrGroupDao 层 getAttrGroupWithAttrsBySpuId 方法:
package com.atguigu.gulimall.product.dao;
@Mapper
public interface AttrGroupDao extends BaseMapper {
List getAttrGroupWithAttrsBySpuId(@Param("spuId") Long spuId, @Param("catalogId") Long catalogId);
}
gulimall-product/src/main/resources/mapper/product/AttrGroupDao.xml :
这里使用了联表查询:
catelog_id
查询 pms_attr_group 表中对应的 属性分组的信息 attr_group_id
、attr_group_name
attr_group_id
联表查询 pms_attr_attrgroup_relation 表中的属性id attr_id
attr_id
联表查询 pms_attr 表中对应的 attr_name
、attr_id
attr_id
联表查询 pms_product_attr_value 表中对应的 属性值 attr_value
1、添加thymeleaf的名称空间
2、标题名设置
华为 HUAWEI Mate 10 6GB+128GB 亮黑色 移动联通电信4G手机 双卡双待
3、大图显示
4、价格设置
- 京东价
-
¥
4499.00
-
预约享资格
-
预约说明
5、是否有货
无货, 此商品暂时售完
6、小图显示
7、销售属性
- 选择[[${attr.attrName}]]
-
[[${val}]]
8、商品介绍
9、规格包装
主体
- 品牌
- 华为(HUAWEI)
包装清单
手机(含内置电池) X 1、5A大电流华为SuperCharge充电器X 1、5A USB数据线 X 1、半入耳式线控耳机 X 1、快速指南X 1、三包凭证 X 1、取卡针 X 1、保护壳 X 1
需求:通过不同的销售属性渲染sku商品
通过选择销售属性获取该销售属性对应的sku,通过算法选中该sku
在 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 attrValues;
}
修改gulimall-product/src/main/resources/mapper/product/SkuSaleAttrValueDao.xml
- 选择[[${attr.attrName}]]
-
[[${vals.attrValue}]]
添加线程池属性配置类,并注入到容器中
package com.atguigu.gulimall.product.config;
//跟gulimall.thread相关的配置文件绑定
//这个注解设置yml配置文件前缀,这样配置后yml数据就会自动注入到 Bean 中,不用再@Value
@ConfigurationProperties(prefix = "gulimall.thread")
@Component
@Data
public class ThreadPoolConfigProperties {
private Integer coreSize;
private Integer maxSize;
private Integer keepAliveTime;
}
作用是给yml配置加提示,不导入也行。
org.springframework.boot
spring-boot-configuration-processor
true
在gulimall-product服务中加入以下配置:
# 配置线程池
gulimall:
thread:
core-size: 20
max-size: 200
keep-alive-time: 10
这是上面自定义的配置,因为导入了spring-boot-configuration-processor依赖,所以编写时有提示:
获取线程池的属性值这里直接调用与配置文件相对应的属性配置类
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
imageFuture
用supplyAsync而不是runAsync ,以便于获取线程返回结果。
com.xxx.gulimall.product.service.impl.SkuInfoServiceImpl
@Override
public SkuItemVo item(Long skuId) {
SkuItemVo skuItemVo = new SkuItemVo();
// 1、sku基本信息 pms_sku_info
//创建异步对象用supplyAsync而不是runAsync ,以便于获取线程返回结果。
CompletableFuture infoFuture = CompletableFuture.supplyAsync(() -> {
SkuInfoEntity info = getById(skuId);
skuItemVo.setInfo(info);
return info;
}, executor);
// 2、获取 spu 的销售属性组合
//线程串行化用thenAcceptAsync接收第一步的结果即sku实体类,自己执行完没有返回结果
CompletableFuture saleAttrFuture = infoFuture.thenAcceptAsync(res -> {
List saleAttrVos = saleAttrValueService.getSaleAttrsBySpuId(res.getSpuId());
skuItemVo.setSaleAttr(saleAttrVos);
}, executor);
// 3、获取 spu 的介绍 pms_spu_info_desc
//这里也需要第一步的sku实体类,所以还是第一步future的thenAcceptAsync
CompletableFuture descFuture = infoFuture.thenAcceptAsync(res -> {
SpuInfoDescEntity spuInfoDescEntity = spuInfoDescService.getById(res.getSpuId());
skuItemVo.setDesp(spuInfoDescEntity);
}, executor);
// 4、获取 spu 的规格参数信息 pms_spu_info_desc
CompletableFuture baseAttrFuture = infoFuture.thenAcceptAsync(res -> {
List attrGroupVos = attrGroupService.getAttrGroupWithAttrsBySpuId(res.getSpuId(), res.getCatalogId());
skuItemVo.setGroupAttrs(attrGroupVos);
}, executor);
// 5、sku的图片信息 pms_sku_images
//这个任务跟前面几个任务都没关系
//这里创建异步对象用runAsync 而不是supplyAsync,因为不需要获取线程结果
CompletableFuture imageFuture = CompletableFuture.runAsync(() -> {
List images = imagesService.getImagesBySkuId(skuId);
skuItemVo.setImages(images);
}, executor);
// 等待所有任务都完成
//多任务组合,allOf等待所有任务完成。这里就不需要加infoFuture,因为依赖于它结果的saleAttrFuture等都完成了,它肯定也完成了。
CompletableFuture.allOf(saleAttrFuture,descFuture,baseAttrFuture,imageFuture).get();
return skuItemVo;
}