100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > 尚硅谷谷粒商城第十二天 商品详情页及异步编排

尚硅谷谷粒商城第十二天 商品详情页及异步编排

时间:2020-12-29 12:18:33

相关推荐

尚硅谷谷粒商城第十二天 商品详情页及异步编排

1. 商品详情

当用户搜索到商品,肯定会点击查看,就会进入商品详情页,接下来我们完成商品详情页的展示。

商品详情浏览量比较大,并发高,我们会独立开启一个微服务,用来展示商品详情。

1.1. 创建module

pom.xml依赖:

<?xml version="1.0" encoding="UTF-8"?><project xmlns='/POM/4.0.0" xmlns:xsi="/2001/XMLSchema-instance"xsi:schemaLocation="/POM/4.0.0 /xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>com.atguigu</groupId><artifactId>gmall</artifactId><version>0.0.1-SNAPSHOT</version></parent><groupId>com.atguigu</groupId><artifactId>gmall-item</artifactId><version>0.0.1-SNAPSHOT</version><name>gmall-item</name><description>谷粒商城商品详情页</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>com.atguigu</groupId><artifactId>gmall-pms-interface</artifactId><version>0.0.1-SNAPSHOT</version></dependency><dependency><groupId>com.atguigu</groupId><artifactId>gmall-wms-interface</artifactId><version>0.0.1-SNAPSHOT</version></dependency><dependency><groupId>com.atguigu</groupId><artifactId>gmall-sms-interface</artifactId><version>0.0.1-SNAPSHOT</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</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-sentinel</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-zipkin</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

bootstrap.properties:

spring:application:name: item-servicecloud:nacos:config:server-addr: 127.0.0.1:8848

application.properties:

server:port: 18088spring:cloud:nacos:discovery:server-addr: 127.0.0.1:8848sentinel:transport:dashboard: localhost:8080port: 8719zipkin:base-url: http://localhost:9411discovery-client-enabled: falsesender:type: websleuth:sampler:probability: 1redis:host: 172.16.116.100feign:sentinel:enabled: truelogging:level:com.atguigu.gmall: debug

GmallItemApplication:

@SpringBootApplication@EnableDiscoveryClient@EnableFeignClientspublic class GmallItemApplication {public static void main(String[] args) {SpringApplication.run(GmallItemApplication.class, args);}}

别忘了配置网关:

1.2. 数据模型

当点击搜索列表中的一个记录,会跳转到商品详情页。这个商品详情页是一个spu?还是sku

以京东为例:

在京东搜索小米,出现搜索列表后。点击其中一条记录,跳转到商品详情页,这个商品详情页展示的是:

结合页面,商品详情页需要的数据有:

面包屑信息:

三级分类品牌spu的名称

sku相关信息:

sku的基本信息(标题、副标题、价格、大图片等)sku的所有图片sku的所有促销信息sku的库存情况(是否有货)

spu下所有销售组合:

每个销售属性可取值集合,方便渲染可选值列表当前商品的销售属性,方便渲染选中项销售属性组合与skuId的映射关系,方便切换sku

商品详情信息:

spu的描述信息spu的所有基本规格分组及规格参数

最终设计如下:

商品详情页总的数据模型:ItemVO

@Datapublic class ItemVo {// 三级分类private List<CategoryEntity> categories;// 品牌private Long brandId;private String brandName;// spuprivate Long spuId;private String spuName;// skuprivate 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:

@Datapublic class ItemSaleVo {private String type; // 积分 满减 打折private String desc; // 描述信息}

ItemGroupVo:

@Datapublic class ItemGroupVo {private String groupName;private List<AttrValueVo> attrValues;}

AttrValueVo:

@Datapublic class AttrValueVo {private Long attrId;private String attrName;private String attrValue;}

SaleAttrValueVo:

@Datapublic class SaleAttrValueVo {private Long attrId;private String attrName;private Set<String> attrValues;}

1.3. 远程调用接口

跳转到商品详情页时,已知条件只有一个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查询分组及组下的规格参数值

1.3.1. 添加远程接口

这些数据模型需要调用远程接口从其他微服务获取,所以这里先编写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);

1.3.2. 根据三级分类id查询一二三级分类

在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实现类中添加方法:

@Overridepublic 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);

1.3.3. 根据skuId查询sku的图片

在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);

1.3.4. 查询sku的营销信息

在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中实现接口方法:

@Overridepublic 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);

1.3.5. 根据spuId查询spu下的所有销售属性

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中实现该方法:

@Autowiredprivate SkuAttrValueMapper skuAttrValueMapper;@Overridepublic List<SaleAttrValueVo> querySkuAttrValuesBySpuId(Long spuId) {List<AttrValueVo> attrValueVos = skuAttrValueMapper.querySkuAttrValuesBySpuId(spuId);// 以attrId进行分组Map<Long, List<AttrValueVo>> map = attrValueVos.stream().collect(groupingBy(AttrValueVo::getAttrId));// 创建一个List<SaleAttrValueVo>List<SaleAttrValueVo> saleAttrValueVos = new ArrayList<>();map.forEach((attrId, attrs) -> {SaleAttrValueVo saleAttrValueVo = new SaleAttrValueVo();// attrIdsaleAttrValueVo.setAttrId(attrId);// attrNamesaleAttrValueVo.setAttrName(attrs.get(0).getAttrName());// attrValuesSet<String> attrValues = attrs.stream().map(AttrValueVo::getAttrValue).collect(Collectors.toSet());saleAttrValueVo.setAttrValues(attrValues);saleAttrValueVos.add(saleAttrValueVo);});return saleAttrValueVos;}

SkuAttrValueMapper接口添加方法:

@Mapperpublic interface SkuAttrValueMapper extends BaseMapper<SkuAttrValueEntity> {List<AttrValueVo> querySkuAttrValuesBySpuId(Long spuId);}

SkuAttrValueMapper.xml中配置映射:

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-////DTD Mapper 3.0//EN" "/dtd/mybatis-3-mapper.dtd"><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_valuefrom pms_sku_attr_value a INNER JOIN pms_sku b on a.sku_id=b.idwhere spu_id=#{spuId};</select></mapper>

在GmallPmsApi中添加数据接口:

@GetMapping("pms/skuattrvalue/spu/{spuId}")public ResponseVo<List<SaleAttrValueVo>> querySkuAttrValuesBySpuId(@PathVariable("spuId")Long spuId);

1.3.6. 查询spu下skuId及销售属性的关系

给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实现类实现接口方法:

@Overridepublic 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方法:

@Mapperpublic 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_idfrom pms_sku_attr_value a INNER JOIN pms_sku b on a.sku_id=b.idwhere 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);

1.3.7. 查询组及组下参数和值

在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实现类中添加实现方法:

@Autowiredprivate SpuAttrValueMapper spuAttrValueMapper;@Autowiredprivate SkuAttrValueMapper skuAttrValueMapper;@Overridepublic 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.遍历分组查询每个组下的attrreturn 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));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());}

1.4. 给商品详情页提供数据

请求路径:/item/{skuId}

请求参数:skuId

请求方式:GET

响应:ItemVO的json数据

ItemController:

@RestController@RequestMapping("item")public class ItemController {@Autowiredprivate ItemService itemService;@GetMapping("{skuId}")public ResponseVo<ItemVo> load(@PathVariable("skuId")Long skuId){ItemVo itemVo = this.itemService.load(skuId);return ResponseVo.ok(itemVo);}}

ItemService:

@Servicepublic class ItemService {@Autowiredprivate GmallPmsClient pmsClient;@Autowiredprivate GmallWmsClient wmsClient;@Autowiredprivate GmallSmsClient smsClient;@Autowiredprivate ThreadPoolExecutor threadPoolExecutor;public ItemVo load(Long skuId) {ItemVo itemVo = new ItemVo();// 根据skuId查询sku的信息1ResponseVo<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查询分类信息2ResponseVo<List<CategoryEntity>> categoryResponseVo = this.pmsClient.queryCategoriesByCid3(skuEntity.getCategoryId());List<CategoryEntity> categoryEntities = categoryResponseVo.getData();itemVo.setCategories(categoryEntities);// 根据品牌的id查询品牌3ResponseVo<BrandEntity> brandEntityResponseVo = this.pmsClient.queryBrandById(skuEntity.getBrandId());BrandEntity brandEntity = brandEntityResponseVo.getData();if (brandEntity != null) {itemVo.setBrandId(brandEntity.getId());itemVo.setBrandName(brandEntity.getName());}// 根据spuId查询spu4ResponseVo<SpuEntity> spuEntityResponseVo = this.pmsClient.querySpuById(skuEntity.getSpuId());SpuEntity spuEntity = spuEntityResponseVo.getData();if (spuEntity != null) {itemVo.setSpuId(spuEntity.getId());itemVo.setSpuName(spuEntity.getName());}// 跟据skuId查询图片5ResponseVo<List<SkuImagesEntity>> skuImagesResponseVo = this.pmsClient.queryImagesBySkuId(skuId);List<SkuImagesEntity> skuImagesEntities = skuImagesResponseVo.getData();itemVo.setImages(skuImagesEntities);// 根据skuId查询sku营销信息6ResponseVo<List<ItemSaleVo>> salesResponseVo = this.smsClient.querySalesBySkuId(skuId);List<ItemSaleVo> sales = salesResponseVo.getData();itemVo.setSales(sales);// 根据skuId查询sku的库存信息7ResponseVo<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的销售属性8ResponseVo<List<SaleAttrValueVo>> saleAttrValueVoResponseVo = this.pmsClient.querySkuAttrValuesBySpuId(skuEntity.getSpuId());List<SaleAttrValueVo> saleAttrValueVos = saleAttrValueVoResponseVo.getData();itemVo.setSaleAttrs(saleAttrValueVos);// 当前sku的销售属性9ResponseVo<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及销售属性的映射关系10ResponseVo<String> skusJsonResponseVo = this.pmsClient.querySkusJsonBySpuId(skuEntity.getSpuId());String skusJson = skusJsonResponseVo.getData();itemVo.setSkusJson(skusJson);// 根据spuId查询spu的海报信息11ResponseVo<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查询组及组下的规格参数及值 12ResponseVo<List<ItemGroupVo>> groupResponseVo = this.pmsClient.queryGoupsWithAttrValues(skuEntity.getCategoryId(), skuEntity.getSpuId(), skuId);List<ItemGroupVo> itemGroupVos = groupResponseVo.getData();itemVo.setGroups(itemGroupVos);return itemVo;}}

完成!!

2. CompletableFuture异步调用

问题:查询商品详情页的逻辑非常复杂,数据的获取都需要远程调用,必然需要花费更多的时间。

假如商品详情页的每个查询,需要如下标注的时间才能完成

// 1. 获取sku的基本信息0.5s// 2. 获取sku的图片信息0.5s// 3. 获取sku的促销信息 TODO 1s// 4. 获取spu的所有销售属性1s// 5. 获取规格参数组及组下的规格参数 TODO1.5s// 6. spu详情 TODO1s.........

那么,用户需要6.5s后才能看到商品详情页的内容。很显然是不能接受的。

如果有多个线程同时完成这6步操作,也许只需要1.5s即可完成响应。

2.1. 线程回顾

初始化线程的4种方式:

继承Thread实现Runnable接口实现Callable接口 + FutureTask (可以拿到返回结果,可以处理异常)线程池

方式1和方式2:主进程无法获取线程的运算结果。不适合当前场景

方式3:主进程可以获取线程的运算结果,并设置给itemVO,但是不利于控制服务器中的线程资源。可以导致服务器资源耗尽。

方式4:通过如下两种方式初始化线程池:

Executors.newFiexedThreadPool(3);//或者new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit unit, workQueue, threadFactory, handler);

通过线程池性能稳定,也可以获取执行结果,并捕获异常。但是,在业务复杂情况下,一个异步调用可能会依赖于另一个异步调用的执行结果。

2.2. CompletableFuture介绍

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接口的实现类,都可以获取线程的执行结果。

2.3. 创建异步对象

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的方法会使用monPool() 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。以下所有的方法都类同。

runAsync方法不支持返回值。supplyAsync可以支持返回值。

2.4. 计算完成时回调方法

当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>() {@Overridepublic Object get() {System.out.println(Thread.currentThread().getName() + "\t completableFuture");int i = 10 / 0;return 1024;}}).whenComplete(new BiConsumer<Object, Throwable>() {@Overridepublic void accept(Object o, Throwable throwable) {System.out.println("-------o=" + o.toString());System.out.println("-------throwable=" + throwable);}}).exceptionally(new Function<Throwable, Object>() {@Overridepublic Object apply(Throwable throwable) {System.out.println("throwable=" + throwable);return 6666;}});System.out.println(future.get());}}

2.5. 线程串行化方法

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>() {@Overridepublic Integer get() {System.out.println(Thread.currentThread().getName() + "\t completableFuture");//int i = 10 / 0;return 1024;}}).thenApply(new Function<Integer, Integer>() {@Overridepublic Integer apply(Integer o) {System.out.println("thenApply方法,上次返回结果:" + o);return o * 2;}}).whenComplete(new BiConsumer<Integer, Throwable>() {@Overridepublic void accept(Integer o, Throwable throwable) {System.out.println("-------o=" + o);System.out.println("-------throwable=" + throwable);}}).exceptionally(new Function<Throwable, Integer>() {@Overridepublic Integer apply(Throwable throwable) {System.out.println("throwable=" + throwable);return 6666;}});System.out.println(future.get());}

2.6. 多任务组合

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(pletedFuture("hello"),pletedFuture(" world!"),pletedFuture(" hello"),pletedFuture("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:helloget future at:1568892339473, result: world!get future at:1568892339473, result: helloget future at:1568892339473, result:java!

几乎同时完成任务!

2.7. 优化商品详情页

@Servicepublic class ItemService {@Autowiredprivate GmallPmsClient pmsClient;@Autowiredprivate GmallWmsClient wmsClient;@Autowiredprivate GmallSmsClient smsClient;@Autowiredprivate ThreadPoolExecutor threadPoolExecutor;public ItemVo load(Long skuId) {ItemVo itemVo = new ItemVo();// 根据skuId查询sku的信息1CompletableFuture<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查询分类信息2CompletableFuture<Void> categoryCompletableFuture = skuCompletableFuture.thenAcceptAsync(skuEntity -> {ResponseVo<List<CategoryEntity>> categoryResponseVo = this.pmsClient.queryCategoriesByCid3(skuEntity.getCategoryId());List<CategoryEntity> categoryEntities = categoryResponseVo.getData();itemVo.setCategories(categoryEntities);}, threadPoolExecutor);// 根据品牌的id查询品牌3CompletableFuture<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查询spu4CompletableFuture<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查询图片5CompletableFuture<Void> skuImagesCompletableFuture = CompletableFuture.runAsync(() -> {ResponseVo<List<SkuImagesEntity>> skuImagesResponseVo = this.pmsClient.queryImagesBySkuId(skuId);List<SkuImagesEntity> skuImagesEntities = skuImagesResponseVo.getData();itemVo.setImages(skuImagesEntities);}, threadPoolExecutor);// 根据skuId查询sku营销信息6CompletableFuture<Void> salesCompletableFuture = CompletableFuture.runAsync(() -> {ResponseVo<List<ItemSaleVo>> salesResponseVo = this.smsClient.querySalesBySkuId(skuId);List<ItemSaleVo> sales = salesResponseVo.getData();itemVo.setSales(sales);}, threadPoolExecutor);// 根据skuId查询sku的库存信息7CompletableFuture<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的海报信息9CompletableFuture<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查询组及组下的规格参数及值 10CompletableFuture<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;}}

3. 前后端联调

把课前资料《前端工程\动态页面》的对应item.html和common目录copy到工程:

并在pom.xml中引入thymeleaf依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>

在application.yml中添加thymeleaf的配置:

修改nginx中的配置,添加域名

网关中添加添加同步路由:

3.1. 跳转到商品详情页

修改ItemController的方法调转到商品详情页面:

@RestController//@RequestMapping("item")public class ItemController {@Autowiredprivate 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.2. 页面结构

参照京东的商品详情页,如下:

主要包含3部分:面包屑、sku大图片及基本信息、页面下方的商品详情。

谷粒商城也是包含这三部分内容,页面源码结构如下:

3.3. 面包屑

<!-- 面包屑:分类 品牌 spu --><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>

3.4. 商品基本信息

3.4.1. 商品图片列表

<!-- 商品大图片列表 --><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">&lt;</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">&gt;</a></div></div></div>

3.4.2. sku基本信息

包含:标题 副标题 价格 促销 重量等等

<!-- 标题 --><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><!--${#numbers.formatDecimal(num,0,'COMMA',2,'POINT')}则显示 .00${#numbers.formatDecimal(num,1,'COMMA',2,'POINT')}则显示 0.00--><em th:text="*{#numbers.formatDecimal(price, 1, 'COMMA', 2, 'POINT')}">5999.00</em><span>降价通知</span></div><div class="fr remark"><i>累计评价</i><em>62344</em></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}">500</em></div></div></div>

3.4.3. 销售属性

<!-- 销售属性 --><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 = `/${this.skuId}.html`}}}})</script>

3.5. 商品详情

商品描述大海报的渲染:

规格参数的渲染:

<!-- 规格参数 --><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>

-for=“attrValue,index in attr.attrValues” :key=“index” @click=“saleAttr[attr.attrId]=attrValue”>

{{attrValue}}

vuejs的代码实现:```html<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 = `/${this.skuId}.html`}}}})</script>

3.5. 商品详情

商品描述大海报的渲染:

[外链图片转存中…(img-gJJcgst5-1649834474678)]

规格参数的渲染:

<!-- 规格参数 --><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>

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。