当用户搜索到商品,肯定会点击查看,就会进入商品详情页,接下来我们完成商品详情页的展示。
商品详情浏览量比较大,并发高,我们会独立开启一个微服务,用来展示商品详情。
pom.xml依赖:
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>com.atguigugroupId>
<artifactId>gmallartifactId>
<version>0.0.1-SNAPSHOTversion>
parent>
<groupId>com.atguigugroupId>
<artifactId>gmall-itemartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>gmall-itemname>
<description>谷粒商城商品详情页description>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>com.atguigugroupId>
<artifactId>gmall-pms-interfaceartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
<dependency>
<groupId>com.atguigugroupId>
<artifactId>gmall-wms-interfaceartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
<dependency>
<groupId>com.atguigugroupId>
<artifactId>gmall-sms-interfaceartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-zipkinartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
exclusion>
exclusions>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
bootstrap.properties:
spring:
application:
name: item-service
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
application.properties:
server:
port: 18088
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
sentinel:
transport:
dashboard: localhost:8080
port: 8719
zipkin:
base-url: http://localhost:9411
discovery-client-enabled: false
sender:
type: web
sleuth:
sampler:
probability: 1
redis:
host: 172.16.116.100
feign:
sentinel:
enabled: true
logging:
level:
com.atguigu.gmall: debug
GmallItemApplication:
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class GmallItemApplication {
public static void main(String[] args) {
SpringApplication.run(GmallItemApplication.class, args);
}
}
别忘了配置网关:
当点击搜索列表中的一个记录,会跳转到商品详情页。这个商品详情页是一个spu?还是sku?
以京东为例:
在京东搜索小米,出现搜索列表后。点击其中一条记录,跳转到商品详情页,这个商品详情页展示的是:
结合页面,商品详情页需要的数据有:
面包屑信息:
sku相关信息:
spu下所有销售组合:
可取值集合
,方便渲染可选值列表
商品详情信息:
最终设计如下:
商品详情页总的数据模型:ItemVO
@Data
public class ItemVo {
// 三级分类
private List<CategoryEntity> categories;
// 品牌
private Long brandId;
private String brandName;
// spu
private Long spuId;
private String spuName;
// sku
private Long skuId;
private String title;
private String subTitle;
private BigDecimal price;
private Integer weight;
private String defaltImage;
// sku图片
private List<SkuImagesEntity> images;
// 营销信息
private List<ItemSaleVo> sales;
// 是否有货
private Boolean store = false;
// sku所属spu下的所有sku的销售属性
// [{attrId: 3, attrName: '颜色', attrValues: '白色','黑色','粉色'},
// {attrId: 8, attrName: '内存', attrValues: '6G','8G','12G'},
// {attrId: 9, attrName: '存储', attrValues: '128G','256G','512G'}]
private List<SaleAttrValueVo> saleAttrs;
// 当前sku的销售属性:{3:'白色',8:'8G',9:'128G'}
private Map<Long, String> saleAttr;
// sku列表:{'白色,8G,128G': 4, '白色,8G,256G': 5, '白色,8G,512G': 6, '白色,12G,128G': 7}
private String skusJson;
// spu的海报信息
private List<String> spuImages;
// 规格参数组及组下的规格参数(带值)
private List<ItemGroupVo> groups;
}
ItemSaleVo:
@Data
public class ItemSaleVo {
private String type; // 积分 满减 打折
private String desc; // 描述信息
}
ItemGroupVo:
@Data
public class ItemGroupVo {
private String groupName;
private List<AttrValueVo> attrValues;
}
AttrValueVo:
@Data
public class AttrValueVo {
private Long attrId;
private String attrName;
private String attrValue;
}
SaleAttrValueVo:
@Data
public class SaleAttrValueVo {
private Long attrId;
private String attrName;
private Set<String> attrValues;
}
跳转到商品详情页时,已知条件只有一个skuId。为渲染商品详情页,需要的远程数据结构提供数据:
根据skuId查询sku(已有)
根据sku中的三级分类id查询一二三级分类
根据sku中的品牌id查询品牌(已有)
根据sku中的spuId查询spu信息(已有)
根据skuId查询sku所有图片
根据skuId查询sku的所有营销信息
根据skuId查询sku的库存信息(已有)
根据sku中的spuId查询spu下的所有销售属性
[
{
attrId: 3,
attrName: '颜色',
attrValues: ['白色', '黑色', '粉色']
},
{
attrId: 8,
attrName: '内存',
attrValues: ['6G', '8G', '12G']
},
{
attrId: 9,
attrName: '存储',
attrValues: ['128G', '256G', '512G']
}
]
根据skuId查询当前sku的销售属性
根据sku中的spuId查询spu下所有sku:销售属性组合与skuId映射关系
{'白色,8G,128G': 4, '白色,8G,256G': 5, '白色,8G,512G': 6, '白色,12G,128G': 7}
根据sku中spuId查询spu的描述信息(已有)
根据分类id、spuId及skuId查询分组及组下的规格参数值
这些数据模型需要调用远程接口从其他微服务获取,所以这里先编写feign接口
GmallPmsClient:
@FeignClient("pms-service")
public interface GmallPmsClient extends GmallPmsApi {
}
GmallSmsClient:
@FeignClient("sms-service")
public interface GmallSmsClient extends GmallSmsApi {
}
GmallWmsClient:
@FeignClient("wms-service")
public interface GmallWmsClient extends GmallWmsApi {
}
有些接口已经有了,直接给GmallPmsApi添加方法即可:
/**
* 根据id查询sku信息
* @param id
* @return
*/
@GetMapping("pms/sku/{id}")
public ResponseVo<SkuEntity> querySkuById(@PathVariable("id") Long id);
/**
* 根据id查询spu信息
* @param id
* @return
*/
@GetMapping("pms/spu/{id}")
public ResponseVo<SpuEntity> querySpuById(@PathVariable("id") Long id);
/**
* 根据id查询spu的描述信息
* @param spuId
* @return
*/
@GetMapping("pms/spudesc/{spuId}")
public ResponseVo<SpuDescEntity> querySpuDescById(@PathVariable("spuId") Long spuId);
在CategoryController中添加方法:
@GetMapping("all/{cid3}")
public ResponseVo<List<CategoryEntity>> queryCategoriesByCid3(@PathVariable("cid3")Long cid3){
List<CategoryEntity> itemCategoryVos = this.categoryService.queryCategoriesByCid3(cid3);
return ResponseVo.ok(itemCategoryVos);
}
在CategoryService接口中添加方法:
List<CategoryEntity> queryCategoriesByCid3(Long cid3);
在CategoryServiceImpl实现类中添加方法:
@Override
public List<CategoryEntity> queryCategoriesByCid3(Long cid3) {
// 查询三级分类
CategoryEntity categoryEntity3 = this.categoryMapper.selectById(cid3);
// 查询二级分类
CategoryEntity categoryEntity2 = this.categoryMapper.selectById(categoryEntity3.getParentId());
// 查询一级分类
CategoryEntity categoryEntity1 = this.categoryMapper.selectById(categoryEntity2.getParentId());
return Arrays.asList(categoryEntity1, categoryEntity2, categoryEntity3);
}
在GmallPmsApi中添加接口方法:
@GetMapping("pms/category/all/{cid3}")
public ResponseVo<List<CategoryEntity>> queryCategoriesByCid3(@PathVariable("cid3")Long cid3);
在SkuImagesController中添加:
@GetMapping("sku/{skuId}")
public ResponseVo<List<SkuImagesEntity>> queryImagesBySkuId(@PathVariable("skuId") Long skuId){
List<SkuImagesEntity> imagesEntities = this.skuImagesService.list(new QueryWrapper<SkuImagesEntity>().eq("sku_id", skuId));
return ResponseVo.ok(imagesEntities);
}
在GmallPmsApi中添加接口方法:
@GetMapping("pms/skuimages/sku/{skuId}")
public ResponseVo<List<SkuImagesEntity>> queryImagesBySkuId(@PathVariable("skuId")Long skuId);
在SkuBoundsController中添加查询营销信息的方法:
@GetMapping("sku/{skuId}")
public ResponseVo<List<ItemSaleVo>> querySalesBySkuId(@PathVariable("skuId")Long skuId){
List<ItemSaleVo> itemSaleVos = this.skuBoundsService.querySalesBySkuId(skuId);
return ResponseVo.ok(itemSaleVos);
}
在SkuBoundsService中添加接口方法:
List<ItemSaleVo> querySalesBySkuId(Long skuId);
在SkuBoundsServiceImpl中实现接口方法:
@Override
public List<ItemSaleVo> querySalesBySkuId(Long skuId) {
List<ItemSaleVo> itemSaleVos = new ArrayList<>();
// 查询积分信息
SkuBoundsEntity skuBoundsEntity = this.getOne(new QueryWrapper<SkuBoundsEntity>().eq("sku_id", skuId));
ItemSaleVo bounds = new ItemSaleVo();
bounds.setType("积分");
bounds.setDesc("送" + skuBoundsEntity.getGrowBounds() + "成长积分,送" + skuBoundsEntity.getBuyBounds() + "购物积分");
itemSaleVos.add(bounds);
// 查询满减信息
SkuFullReductionEntity reductionEntity = this.reductionMapper.selectOne(new QueryWrapper<SkuFullReductionEntity>().eq("sku_id", skuId));
ItemSaleVo reduction = new ItemSaleVo();
reduction.setType("满减");
reduction.setDesc("满" + reductionEntity.getFullPrice() + "减" + reductionEntity.getReducePrice());
itemSaleVos.add(reduction);
// 查询打折信息
SkuLadderEntity ladderEntity = this.ladderMapper.selectOne(new QueryWrapper<SkuLadderEntity>().eq("sku_id", skuId));
ItemSaleVo ladder = new ItemSaleVo();
ladder.setType("打折");
ladder.setDesc("满" + ladderEntity.getFullCount() + "件打" + ladderEntity.getDiscount().divide(new BigDecimal(10)) + "折");
itemSaleVos.add(ladder);
return itemSaleVos;
}
在GmallSmsApi中添加数据接口方法
@GetMapping("sms/skubounds/sku/{skuId}")
public ResponseVo<List<ItemSaleVo>> querySalesBySkuId(@PathVariable("skuId")Long skuId);
SkuAttrValueController中添加查询方法:
@GetMapping("spu/{spuId}")
public ResponseVo<List<SaleAttrValueVo>> querySkuAttrValuesBySpuId(@PathVariable("spuId")Long spuId){
List<SaleAttrValueVo> saleAttrValueVos = this.skuAttrValueService.querySkuAttrValuesBySpuId(spuId);
return ResponseVo.ok(saleAttrValueVos);
}
在SkuSaleAttrValueService接口中添加抽象方法:
List<SaleAttrValueVo> querySkuAttrValuesBySpuId(Long spuId);
在SkuSaleAttrValueServiceImpl中实现该方法:
@Autowired
private SkuAttrValueMapper skuAttrValueMapper;
@Override
public List<SaleAttrValueVo> querySkuAttrValuesBySpuId(Long spuId) {
List<AttrValueVo> attrValueVos = skuAttrValueMapper.querySkuAttrValuesBySpuId(spuId);
// 以attrId进行分组
Map<Long, List<AttrValueVo>> map = attrValueVos.stream().collect(groupingBy(AttrValueVo::getAttrId));
// 创建一个List
List<SaleAttrValueVo> saleAttrValueVos = new ArrayList<>();
map.forEach((attrId, attrs) -> {
SaleAttrValueVo saleAttrValueVo = new SaleAttrValueVo();
// attrId
saleAttrValueVo.setAttrId(attrId);
// attrName
saleAttrValueVo.setAttrName(attrs.get(0).getAttrName());
// attrValues
Set<String> attrValues = attrs.stream().map(AttrValueVo::getAttrValue).collect(Collectors.toSet());
saleAttrValueVo.setAttrValues(attrValues);
saleAttrValueVos.add(saleAttrValueVo);
});
return saleAttrValueVos;
}
SkuAttrValueMapper接口添加方法:
@Mapper
public interface SkuAttrValueMapper extends BaseMapper<SkuAttrValueEntity> {
List<AttrValueVo> querySkuAttrValuesBySpuId(Long spuId);
}
SkuAttrValueMapper.xml中配置映射:
<mapper namespace="com.atguigu.gmall.pms.mapper.SkuAttrValueMapper">
<select id="querySkuAttrValuesBySpuId" resultType="com.atguigu.gmall.pms.vo.AttrValueVo">
select a.attr_id,attr_name,a.attr_value
from pms_sku_attr_value a INNER JOIN pms_sku b on a.sku_id=b.id
where spu_id=#{spuId};
select>
mapper>
在GmallPmsApi中添加数据接口:
@GetMapping("pms/skuattrvalue/spu/{spuId}")
public ResponseVo<List<SaleAttrValueVo>> querySkuAttrValuesBySpuId(@PathVariable("spuId")Long spuId);
给SkuAttrValueController添加方法:
@GetMapping("spu/sku/{spuId}")
public ResponseVo<String> querySkusJsonBySpuId(@PathVariable("spuId") Long spuId){
String skusJson = this.skuAttrValueService.querySkusJsonBySpuId(spuId);
return ResponseVo.ok(skusJson);
}
给SkuAttrValueService接口添加方法:
String querySkusJsonBySpuId(Long spuId);
SkuAttrValueServiceImpl实现类实现接口方法:
@Override
public String querySkusJsonBySpuId(Long spuId) {
// [{"sku_id": 3, "attr_values": "暗夜黑,12G,512G"}, {"sku_id": 4, "attr_values": "白天白,12G,512G"}]
List<Map<String, Object>> skus = this.skuAttrValueMapper.querySkusJsonBySpuId(spuId);
// 转换成:{'暗夜黑,12G,512G': 3, '白天白,12G,512G': 4}
Map<String, Long> map = skus.stream().collect(Collectors.toMap(sku -> sku.get("attr_values").toString(), sku -> (Long) sku.get("sku_id")));
return JSON.toJSONString(map);
}
给SkuAttrValueMapper添加querySkusJsonBySpuId方法:
@Mapper
public interface SkuAttrValueMapper extends BaseMapper<SkuAttrValueEntity> {
List<AttrValueVo> querySkuAttrValuesBySpuId(Long spuId);
List<Map<String, Object>> querySkusJsonBySpuId(Long spuId);
}
给SkuAttrValueMapper.xml映射文件添加映射:
<select id="querySkusJsonBySpuId" resultType="hashmap">
select GROUP_CONCAT(a.attr_value) as attr_values, a.sku_id
from pms_sku_attr_value a INNER JOIN pms_sku b on a.sku_id=b.id
where b.spu_id=#{spuId} group by a.sku_id
select>
给GmallPmsApi添加接口方法:
@GetMapping("pms/skuattrvalue/spu/sku/{spuId}")
public ResponseVo<String> querySkusJsonBySpuId(@PathVariable("spuId") Long spuId);
在AttrGroupController中添加方法:
@GetMapping("withattrvalues")
public ResponseVo<List<ItemGroupVo>> queryGroupsBySpuIdAndCid(
@RequestParam("spuId")Long spuId,
@RequestParam("skuId")Long skuId,
@RequestParam("cid")Long cid
){
List<ItemGroupVo> itemGroupVOS = attrGroupService.queryGroupsBySpuIdAndCid(spuId, skuId, cid);
return ResponseVo.ok(itemGroupVOS);
}
在AttrGroupService接口中添加接口方法:
List<ItemGroupVo> queryGroupsBySpuIdAndCid(Long spuId, Long skuId, Long cid);
在AttrGroupServiceImpl实现类中添加实现方法:
@Autowired
private SpuAttrValueMapper spuAttrValueMapper;
@Autowired
private SkuAttrValueMapper skuAttrValueMapper;
@Override
public List<ItemGroupVo> queryGroupsBySpuIdAndCid(Long spuId, Long skuId, Long cid) {
// 1.根据cid查询分组
List<AttrGroupEntity> attrGroupEntities = this.list(new QueryWrapper<AttrGroupEntity>().eq("category_id", cid));
if (CollectionUtils.isEmpty(attrGroupEntities)){
return null;
}
// 2.遍历分组查询每个组下的attr
return attrGroupEntities.stream().map(group -> {
ItemGroupVo itemGroupVo = new ItemGroupVo();
itemGroupVo.setGroupId(group.getId());
itemGroupVo.setGroupName(group.getName());
List<AttrEntity> attrEntities = this.attrMapper.selectList(new QueryWrapper<AttrEntity>().eq("group_id", group.getId()));
if (!CollectionUtils.isEmpty(attrEntities)){
List<Long> attrIds = attrEntities.stream().map(AttrEntity::getId).collect(Collectors.toList());
// 3.attrId结合spuId查询规格参数对应值
List<SpuAttrValueEntity> spuAttrValueEntities = this.spuAttrValueMapper.selectList(new QueryWrapper<SpuAttrValueEntity>().eq("spu_id", spuId).in("attr_id", attrIds));
// 4.attrId结合skuId查询规格参数对应值
List<SkuAttrValueEntity> skuAttrValueEntities = this.skuAttrValueMapper.selectList(new QueryWrapper<SkuAttrValueEntity>().eq("sku_id", skuId).in("attr_id", attrIds));
List<AttrValueVo> attrValueVos = new ArrayList<>();
if (!CollectionUtils.isEmpty(spuAttrValueEntities)){
List<AttrValueVo> spuAttrValueVos = spuAttrValueEntities.stream().map(attrValue -> {
AttrValueVo attrValueVo = new AttrValueVo();
BeanUtils.copyProperties(attrValue, attrValueVo);
return attrValueVo;
}).collect(Collectors.toList());
attrValueVos.addAll(spuAttrValueVos);
}
if (!CollectionUtils.isEmpty(skuAttrValueEntities)){
List<AttrValueVo> skuAttrValueVos = skuAttrValueEntities.stream().map(attrValue -> {
AttrValueVo attrValueVo = new AttrValueVo();
BeanUtils.copyProperties(attrValue, attrValueVo);
return attrValueVo;
}).collect(Collectors.toList());
attrValueVos.addAll(skuAttrValueVos);
}
itemGroupVo.setAttrValues(attrValueVos);
}
return itemGroupVo;
}).collect(Collectors.toList());
}
请求路径:/item/{skuId}
请求参数:skuId
请求方式:GET
响应:ItemVO的json数据
ItemController:
@RestController
@RequestMapping("item")
public class ItemController {
@Autowired
private ItemService itemService;
@GetMapping("{skuId}")
public ResponseVo<ItemVo> load(@PathVariable("skuId")Long skuId){
ItemVo itemVo = this.itemService.load(skuId);
return ResponseVo.ok(itemVo);
}
}
ItemService:
@Service
public class ItemService {
@Autowired
private GmallPmsClient pmsClient;
@Autowired
private GmallWmsClient wmsClient;
@Autowired
private GmallSmsClient smsClient;
@Autowired
private ThreadPoolExecutor threadPoolExecutor;
public ItemVo load(Long skuId) {
ItemVo itemVo = new ItemVo();
// 根据skuId查询sku的信息1
ResponseVo<SkuEntity> skuEntityResponseVo = this.pmsClient.querySkuById(skuId);
SkuEntity skuEntity = skuEntityResponseVo.getData();
if (skuEntity == null) {
return null;
}
itemVo.setSkuId(skuId);
itemVo.setTitle(skuEntity.getTitle());
itemVo.setSubTitle(skuEntity.getSubtitle());
itemVo.setPrice(skuEntity.getPrice());
itemVo.setWeight(skuEntity.getWeight());
itemVo.setDefaltImage(skuEntity.getDefaultImage());
// 根据cid3查询分类信息2
ResponseVo<List<CategoryEntity>> categoryResponseVo = this.pmsClient.queryCategoriesByCid3(skuEntity.getCategoryId());
List<CategoryEntity> categoryEntities = categoryResponseVo.getData();
itemVo.setCategories(categoryEntities);
// 根据品牌的id查询品牌3
ResponseVo<BrandEntity> brandEntityResponseVo = this.pmsClient.queryBrandById(skuEntity.getBrandId());
BrandEntity brandEntity = brandEntityResponseVo.getData();
if (brandEntity != null) {
itemVo.setBrandId(brandEntity.getId());
itemVo.setBrandName(brandEntity.getName());
}
// 根据spuId查询spu4
ResponseVo<SpuEntity> spuEntityResponseVo = this.pmsClient.querySpuById(skuEntity.getSpuId());
SpuEntity spuEntity = spuEntityResponseVo.getData();
if (spuEntity != null) {
itemVo.setSpuId(spuEntity.getId());
itemVo.setSpuName(spuEntity.getName());
}
// 跟据skuId查询图片5
ResponseVo<List<SkuImagesEntity>> skuImagesResponseVo = this.pmsClient.queryImagesBySkuId(skuId);
List<SkuImagesEntity> skuImagesEntities = skuImagesResponseVo.getData();
itemVo.setImages(skuImagesEntities);
// 根据skuId查询sku营销信息6
ResponseVo<List<ItemSaleVo>> salesResponseVo = this.smsClient.querySalesBySkuId(skuId);
List<ItemSaleVo> sales = salesResponseVo.getData();
itemVo.setSales(sales);
// 根据skuId查询sku的库存信息7
ResponseVo<List<WareSkuEntity>> wareSkuResponseVo = this.wmsClient.queryWareSkusBySkuId(skuId);
List<WareSkuEntity> wareSkuEntities = wareSkuResponseVo.getData();
if (!CollectionUtils.isEmpty(wareSkuEntities)) {
itemVo.setStore(wareSkuEntities.stream().anyMatch(wareSkuEntity -> wareSkuEntity.getStock() - wareSkuEntity.getStockLocked() > 0));
}
// 根据spuId查询spu下的所有sku的销售属性8
ResponseVo<List<SaleAttrValueVo>> saleAttrValueVoResponseVo = this.pmsClient.querySkuAttrValuesBySpuId(skuEntity.getSpuId());
List<SaleAttrValueVo> saleAttrValueVos = saleAttrValueVoResponseVo.getData();
itemVo.setSaleAttrs(saleAttrValueVos);
// 当前sku的销售属性9
ResponseVo<List<SkuAttrValueEntity>> saleAttrResponseVo = this.pmsClient.querySkuAttrValuesBySkuId(skuId);
List<SkuAttrValueEntity> skuAttrValueEntities = saleAttrResponseVo.getData();
Map<Long, String> map = skuAttrValueEntities.stream().collect(Collectors.toMap(SkuAttrValueEntity::getAttrId, SkuAttrValueEntity::getAttrValue));
itemVo.setSaleAttr(map);
// 根据spuId查询spu下的所有sku及销售属性的映射关系10
ResponseVo<String> skusJsonResponseVo = this.pmsClient.querySkusJsonBySpuId(skuEntity.getSpuId());
String skusJson = skusJsonResponseVo.getData();
itemVo.setSkusJson(skusJson);
// 根据spuId查询spu的海报信息11
ResponseVo<SpuDescEntity> spuDescEntityResponseVo = this.pmsClient.querySpuDescById(skuEntity.getSpuId());
SpuDescEntity spuDescEntity = spuDescEntityResponseVo.getData();
if (spuDescEntity != null && StringUtils.isNotBlank(spuDescEntity.getDecript())) {
String[] images = StringUtils.split(spuDescEntity.getDecript(), ",");
itemVo.setSpuImages(Arrays.asList(images));
}
// 根据cid3 spuId skuId查询组及组下的规格参数及值 12
ResponseVo<List<ItemGroupVo>> groupResponseVo = this.pmsClient.queryGoupsWithAttrValues(skuEntity.getCategoryId(), skuEntity.getSpuId(), skuId);
List<ItemGroupVo> itemGroupVos = groupResponseVo.getData();
itemVo.setGroups(itemGroupVos);
return itemVo;
}
}
完成!!
问题:查询商品详情页的逻辑非常复杂,数据的获取都需要远程调用,必然需要花费更多的时间。
假如商品详情页的每个查询,需要如下标注的时间才能完成
// 1. 获取sku的基本信息 0.5s
// 2. 获取sku的图片信息 0.5s
// 3. 获取sku的促销信息 TODO 1s
// 4. 获取spu的所有销售属性 1s
// 5. 获取规格参数组及组下的规格参数 TODO 1.5s
// 6. spu详情 TODO 1s
.........
那么,用户需要6.5s后才能看到商品详情页的内容。很显然是不能接受的。
如果有多个线程同时完成这6步操作,也许只需要1.5s即可完成响应。
初始化线程的4种方式:
方式1和方式2:主进程无法获取线程的运算结果。不适合当前场景
方式3:主进程可以获取线程的运算结果,并设置给itemVO,但是不利于控制服务器中的线程资源。可以导致服务器资源耗尽。
方式4:通过如下两种方式初始化线程池:
Executors.newFiexedThreadPool(3);
//或者
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit unit, workQueue, threadFactory, handler);
通过线程池性能稳定,也可以获取执行结果,并捕获异常。但是,在业务复杂情况下,一个异步调用可能会依赖于另一个异步调用的执行结果。
Future是Java 5添加的类,用来描述一个异步计算的结果。你可以使用isDone
方法检查计算是否完成,或者使用get
阻塞住调用线程,直到计算完成返回结果,你也可以使用cancel
方法停止任务的执行。
虽然Future
以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,只能通过阻塞或者轮询的方式得到任务的结果。阻塞的方式显然和我们的异步编程的初衷相违背,轮询的方式又会耗费无谓的CPU资源,而且也不能及时地得到计算结果,为什么不能用观察者设计模式当计算结果完成及时通知监听者呢?
很多语言,比如Node.js,采用回调的方式实现异步编程。Java的一些框架,比如Netty,自己扩展了Java的 Future
接口,提供了addListener
等多个扩展方法;Google guava也提供了通用的扩展Future;Scala也提供了简单易用且功能强大的Future/Promise异步编程模式。
作为正统的Java类库,是不是应该做点什么,加强一下自身库的功能呢?
在Java 8中, 新增加了一个包含50个方法左右的类: CompletableFuture,提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,提供了函数式编程的能力,可以通过回调的方式处理计算结果,并且提供了转换和组合CompletableFuture的方法。
CompletableFuture类实现了Future接口,所以你还是可以像以前一样通过get
方法阻塞或者轮询的方式获得结果,但是这种方式不推荐使用。
CompletableFuture和FutureTask同属于Future接口的实现类,都可以获取线程的执行结果。
CompletableFuture 提供了四个静态方法来创建一个异步操作。
static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
没有指定Executor的方法会使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。以下所有的方法都类同。
当CompletableFuture的计算结果完成,或者抛出异常的时候,可以执行特定的Action。主要是下面的方法:
public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action);
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action);
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor);
public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn);
whenComplete可以处理正常和异常的计算结果,exceptionally处理异常情况。BiConsumer super T,? super Throwable>可以定义处理业务
whenComplete 和 whenCompleteAsync 的区别:
whenComplete:是执行当前任务的线程执行继续执行 whenComplete 的任务。
whenCompleteAsync:是执行把 whenCompleteAsync 这个任务继续提交给线程池来进行执行。
方法不以Async结尾,意味着Action使用相同的线程执行,而Async可能会使用其他线程执行(如果是使用相同的线程池,也可能会被同一个线程选中执行)
代码示例:
public class CompletableFutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture future = CompletableFuture.supplyAsync(new Supplier<Object>() {
@Override
public Object get() {
System.out.println(Thread.currentThread().getName() + "\t completableFuture");
int i = 10 / 0;
return 1024;
}
}).whenComplete(new BiConsumer<Object, Throwable>() {
@Override
public void accept(Object o, Throwable throwable) {
System.out.println("-------o=" + o.toString());
System.out.println("-------throwable=" + throwable);
}
}).exceptionally(new Function<Throwable, Object>() {
@Override
public Object apply(Throwable throwable) {
System.out.println("throwable=" + throwable);
return 6666;
}
});
System.out.println(future.get());
}
}
thenApply 方法:当一个线程依赖另一个线程时,获取上一个任务返回的结果,并返回当前任务的返回值。
thenAccept方法:消费处理结果。接收任务的处理结果,并消费处理,无返回结果。
thenRun方法:只要上面的任务执行完成,就开始执行thenRun,只是处理完任务后,执行 thenRun的后续操作
带有Async默认是异步执行的。这里所谓的异步指的是不在当前线程内执行。
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)
public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor);
public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action,Executor executor);
Function super T,? extends U>
T:上一个任务返回结果的类型
U:当前任务的返回值类型
代码演示:
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
System.out.println(Thread.currentThread().getName() + "\t completableFuture");
//int i = 10 / 0;
return 1024;
}
}).thenApply(new Function<Integer, Integer>() {
@Override
public Integer apply(Integer o) {
System.out.println("thenApply方法,上次返回结果:" + o);
return o * 2;
}
}).whenComplete(new BiConsumer<Integer, Throwable>() {
@Override
public void accept(Integer o, Throwable throwable) {
System.out.println("-------o=" + o);
System.out.println("-------throwable=" + throwable);
}
}).exceptionally(new Function<Throwable, Integer>() {
@Override
public Integer apply(Throwable throwable) {
System.out.println("throwable=" + throwable);
return 6666;
}
});
System.out.println(future.get());
}
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs);
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs);
allOf:等待所有任务完成
anyOf:只要有一个任务完成
public static void main(String[] args) {
List<CompletableFuture> futures = Arrays.asList(CompletableFuture.completedFuture("hello"),
CompletableFuture.completedFuture(" world!"),
CompletableFuture.completedFuture(" hello"),
CompletableFuture.completedFuture("java!"));
final CompletableFuture<Void> allCompleted = CompletableFuture.allOf(futures.toArray(new CompletableFuture[]{}));
allCompleted.thenRun(() -> {
futures.stream().forEach(future -> {
try {
System.out.println("get future at:"+System.currentTimeMillis()+", result:"+future.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
});
}
测试结果:
get future at:1568892339473, result:hello
get future at:1568892339473, result: world!
get future at:1568892339473, result: hello
get future at:1568892339473, result:java!
几乎同时完成任务!
@Service
public class ItemService {
@Autowired
private GmallPmsClient pmsClient;
@Autowired
private GmallWmsClient wmsClient;
@Autowired
private GmallSmsClient smsClient;
@Autowired
private ThreadPoolExecutor threadPoolExecutor;
public ItemVo load(Long skuId) {
ItemVo itemVo = new ItemVo();
// 根据skuId查询sku的信息1
CompletableFuture<SkuEntity> skuCompletableFuture = CompletableFuture.supplyAsync(() -> {
ResponseVo<SkuEntity> skuEntityResponseVo = this.pmsClient.querySkuById(skuId);
SkuEntity skuEntity = skuEntityResponseVo.getData();
if (skuEntity == null) {
return null;
}
itemVo.setSkuId(skuId);
itemVo.setTitle(skuEntity.getTitle());
itemVo.setSubTitle(skuEntity.getSubtitle());
itemVo.setPrice(skuEntity.getPrice());
itemVo.setWeight(skuEntity.getWeight());
itemVo.setDefaltImage(skuEntity.getDefaultImage());
return skuEntity;
}, threadPoolExecutor);
// 根据cid3查询分类信息2
CompletableFuture<Void> categoryCompletableFuture = skuCompletableFuture.thenAcceptAsync(skuEntity -> {
ResponseVo<List<CategoryEntity>> categoryResponseVo = this.pmsClient.queryCategoriesByCid3(skuEntity.getCategoryId());
List<CategoryEntity> categoryEntities = categoryResponseVo.getData();
itemVo.setCategories(categoryEntities);
}, threadPoolExecutor);
// 根据品牌的id查询品牌3
CompletableFuture<Void> brandCompletableFuture = skuCompletableFuture.thenAcceptAsync(skuEntity -> {
ResponseVo<BrandEntity> brandEntityResponseVo = this.pmsClient.queryBrandById(skuEntity.getBrandId());
BrandEntity brandEntity = brandEntityResponseVo.getData();
if (brandEntity != null) {
itemVo.setBrandId(brandEntity.getId());
itemVo.setBrandName(brandEntity.getName());
}
}, threadPoolExecutor);
// 根据spuId查询spu4
CompletableFuture<Void> spuCompletableFuture = skuCompletableFuture.thenAcceptAsync(skuEntity -> {
ResponseVo<SpuEntity> spuEntityResponseVo = this.pmsClient.querySpuById(skuEntity.getSpuId());
SpuEntity spuEntity = spuEntityResponseVo.getData();
if (spuEntity != null) {
itemVo.setSpuId(spuEntity.getId());
itemVo.setSpuName(spuEntity.getName());
}
}, threadPoolExecutor);
// 跟据skuId查询图片5
CompletableFuture<Void> skuImagesCompletableFuture = CompletableFuture.runAsync(() -> {
ResponseVo<List<SkuImagesEntity>> skuImagesResponseVo = this.pmsClient.queryImagesBySkuId(skuId);
List<SkuImagesEntity> skuImagesEntities = skuImagesResponseVo.getData();
itemVo.setImages(skuImagesEntities);
}, threadPoolExecutor);
// 根据skuId查询sku营销信息6
CompletableFuture<Void> salesCompletableFuture = CompletableFuture.runAsync(() -> {
ResponseVo<List<ItemSaleVo>> salesResponseVo = this.smsClient.querySalesBySkuId(skuId);
List<ItemSaleVo> sales = salesResponseVo.getData();
itemVo.setSales(sales);
}, threadPoolExecutor);
// 根据skuId查询sku的库存信息7
CompletableFuture<Void> storeCompletableFuture = CompletableFuture.runAsync(() -> {
ResponseVo<List<WareSkuEntity>> wareSkuResponseVo = this.wmsClient.queryWareSkusBySkuId(skuId);
List<WareSkuEntity> wareSkuEntities = wareSkuResponseVo.getData();
if (!CollectionUtils.isEmpty(wareSkuEntities)) {
itemVo.setStore(wareSkuEntities.stream().anyMatch(wareSkuEntity -> wareSkuEntity.getStock() - wareSkuEntity.getStockLocked() > 0));
}
}, threadPoolExecutor);
// 根据spuId查询spu下的所有sku的销售属性
CompletableFuture<Void> saleAttrsCompletableFuture = skuCompletableFuture.thenAcceptAsync(skuEntity -> {
ResponseVo<List<SaleAttrValueVo>> saleAttrValueVoResponseVo = this.pmsClient.querySkuAttrValuesBySpuId(skuEntity.getSpuId());
List<SaleAttrValueVo> saleAttrValueVos = saleAttrValueVoResponseVo.getData();
itemVo.setSaleAttrs(saleAttrValueVos);
}, threadPoolExecutor);
// 当前sku的销售属性
CompletableFuture<Void> saleAttrCompletableFuture = CompletableFuture.runAsync(() -> {
ResponseVo<List<SkuAttrValueEntity>> saleAttrResponseVo = this.pmsClient.querySkuAttrValuesBySkuId(skuId);
List<SkuAttrValueEntity> skuAttrValueEntities = saleAttrResponseVo.getData();
Map<Long, String> map = skuAttrValueEntities.stream().collect(Collectors.toMap(SkuAttrValueEntity::getAttrId, SkuAttrValueEntity::getAttrValue));
itemVo.setSaleAttr(map);
}, threadPoolExecutor);
// 根据spuId查询spu下的所有sku及销售属性的映射关系
CompletableFuture<Void> skusJsonCompletableFuture = skuCompletableFuture.thenAcceptAsync(skuEntity -> {
ResponseVo<String> skusJsonResponseVo = this.pmsClient.querySkusJsonBySpuId(skuEntity.getSpuId());
String skusJson = skusJsonResponseVo.getData();
itemVo.setSkusJson(skusJson);
}, threadPoolExecutor);
// 根据spuId查询spu的海报信息9
CompletableFuture<Void> spuImagesCompletableFuture = skuCompletableFuture.thenAcceptAsync(skuEntity -> {
ResponseVo<SpuDescEntity> spuDescEntityResponseVo = this.pmsClient.querySpuDescById(skuEntity.getSpuId());
SpuDescEntity spuDescEntity = spuDescEntityResponseVo.getData();
if (spuDescEntity != null && StringUtils.isNotBlank(spuDescEntity.getDecript())) {
String[] images = StringUtils.split(spuDescEntity.getDecript(), ",");
itemVo.setSpuImages(Arrays.asList(images));
}
}, threadPoolExecutor);
// 根据cid3 spuId skuId查询组及组下的规格参数及值 10
CompletableFuture<Void> groupCompletableFuture = skuCompletableFuture.thenAcceptAsync(skuEntity -> {
ResponseVo<List<ItemGroupVo>> groupResponseVo = this.pmsClient.queryGoupsWithAttrValues(skuEntity.getCategoryId(), skuEntity.getSpuId(), skuId);
List<ItemGroupVo> itemGroupVos = groupResponseVo.getData();
itemVo.setGroups(itemGroupVos);
}, threadPoolExecutor);
CompletableFuture.allOf(categoryCompletableFuture, brandCompletableFuture, spuCompletableFuture,
skuImagesCompletableFuture, salesCompletableFuture, storeCompletableFuture, saleAttrsCompletableFuture,
saleAttrCompletableFuture, skusJsonCompletableFuture, spuImagesCompletableFuture, groupCompletableFuture).join();
return itemVo;
}
}
把课前资料《前端工程\动态页面》的对应item.html和common目录copy到工程:
并在pom.xml中引入thymeleaf依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
在application.yml中添加thymeleaf的配置:
修改nginx中的配置,添加item.gmall.com域名
网关中添加添加同步路由:
修改ItemController的方法调转到商品详情页面:
@RestController
//@RequestMapping("item")
public class ItemController {
@Autowired
private ItemService itemService;
@GetMapping("{skuId}.html")
public String load(@PathVariable("skuId")Long skuId, Model model){
ItemVo itemVo = this.itemService.load(skuId);
model.addAttribute("itemVo", itemVo);
return "item";
}
}
参照京东的商品详情页,如下:
主要包含3部分:面包屑、sku大图片及基本信息、页面下方的商品详情。
谷粒商城也是包含这三部分内容,页面源码结构如下:
<div class="crumb-wrap">
<ul class="sui-breadcrumb">
<li th:each="category : *{categories}">
<a href="#" th:text="${category.name}">手机、数码、通讯a>
li>
<li>
<a href="#" th:text="*{brandName}">Apple苹果a>
li>
<li class="active" th:text="*{spuName}">iphone 6S系类li>
ul>
div>
<div class="fl preview-wrap">
<div class="zoom">
<div id="preview" class="spec-preview">
<span class="jqzoom">
<img th:jqimg="*{defaltImage}" th:src="*{defaltImage}" width="400" height="400" />
span>
div>
<div class="spec-scroll">
<a class="prev"><a>
<div class="items">
<ul>
<li th:each="image : *{images}">
<img th:src="${image.url}" th:bimg="${image.url}" onmousemove="preview(this)" />
li>
ul>
div>
<a class="next">>a>
div>
div>
div>
包含:标题 副标题 价格 促销 重量等等
<div class="sku-name">
<h4 th:text="*{title}">Apple iPhone 6s(A1700)64G玫瑰金色 移动通信电信4G手机h4>
div>
<div class="news">
<span th:text="*{subTitle}">推荐选择下方[移动优惠购],手机套餐齐搞定,不用换号,每月还有花费返span>
div>
<div class="summary">
<div class="summary-wrap">
<div class="fl title">
<i>价 格i>
div>
<div class="fl price">
<i>¥i>
<em th:text="*{#numbers.formatDecimal(price, 1, 'COMMA', 2, 'POINT')}">5999.00em>
<span>降价通知span>
div>
<div class="fr remark">
<i>累计评价i>
<em>62344em>
div>
div>
<div class="summary-wrap">
<div class="fl title">
<i>促 销i>
div>
<div class="fl fix-width">
<div th:each="sale : *{sales}">
<i class="red-bg" th:text="${sale.type}">加价购i>
<em class="t-gray" th:text="${sale.desc}">满999.00另加20.00元,或满1999.00另加30.00元,或满2999.00另加40.00元,即可在购物车换购热销商品em>
div>
div>
div>
div>
<div class="support">
<div class="summary-wrap">
<div class="fl title">
<i>支 持i>
div>
<div class="fl fix-width">
<em class="t-gray">以旧换新,闲置手机回收 4G套餐超值抢 礼品购em>
div>
div>
<div class="summary-wrap">
<div class="fl title">
<i>配 送 至i>
div>
<div class="fl fix-width">
<em class="t-gray">上海市松江区大江商厦6层em>
div>
div>
<div class="summary-wrap">
<div class="fl title">
<i>重 量(g)i>
div>
<div class="fl fix-width">
<em class="t-gray" th:text="*{weight}">500em>
div>
div>
div>
<div class="clearfix choose" id="app">
<div id="specification" class="summary-wrap clearfix">
<dl v-for="attr in saleAttrs" :key="attr.attrId">
<dt>
<div class="fl title">
<i>选择{{attr.attrName}}i>
div>
dt>
<dd v-for="attrValue,index in attr.attrValues" :key="index" @click="saleAttr[attr.attrId]=attrValue">
<a href="javascript:;" :class="{'selected':attrValue === saleAttr[attr.attrId]}">{{attrValue}}a>
dd>
dl>
div>
vuejs的代码实现:
<script th:inline="javascript">
new Vue({
el: '#app',
data: {
saleAttrs: [[${itemVo.saleAttrs}]], // 销售属性可选项
saleAttr: [[${itemVo.saleAttr}]],
skusJson: JSON.parse(decodeURI([[${itemVo.skusJson}]])), // 销售属性和skuId的关系
skuId: [[${itemVo.skuId}]]
},
watch: {
saleAttr: {
deep: true,
handler(newSaleAttr) {
console.log(newSaleAttr)
let key = Object.values(newSaleAttr).join(",")
this.skuId = this.skusJson[key]
window.location = `http://item.gmall.com/${this.skuId}.html`
}
}
}
})
script>
商品描述大海报的渲染:
规格参数的渲染:
<div id="two" class="tab-pane">
<div class="Ptable">
<div class="Ptable-item" th:each="group : *{groups}">
<h3 th:text="${group.groupName}">主体h3>
<dl>
<div th:each="attr : ${group.attrValues}">
<dt th:text="${attr.attrName}">品牌dt><dd th:text="${attr.attrValue}">华为(HUAWEI)dd>
div>
dl>
div>
div>
div>