导航:

谷粒商城笔记+踩坑汇总篇

目录

1、ES回顾

2、ES整合商品上架

2.1、分析

2.2、创建sku的es索引库

2.2.1、两种索引库设计方案分析

2.2.2、最终选用的索引库方案,nested类型

2.3、SkuEsModel模型类

2.4、【库存模块】库存量查询

2.5、【查询模块】保存ES文档

2.5.1、常量类

2.5.2、controller

2.5.3、service

2.6、【商品模块】上架单个spu

2.6.1、controller

2.6.2、远程调用库存模块

2.6.3、【公共模块】商品常量类,添加上传成功或失败的状态码

2.6.4、service

2.6.5、测试

2.6.6、修改结果类R


1、ES回顾

elasticsearch基础1——索引、文档_elasticsearch索引 文档_vincewm的博客-CSDN博客

elasticsearch基础2——DSL查询文档,黑马旅游项目查询功能_elasticsearch查询文档_vincewm的博客-CSDN博客elasticsearch基础3——聚合、补全、集群_vincewm的博客-CSDN博客elasticsearch基础2——DSL查询文档,黑马旅游项目查询功能_elasticsearch查询文档_vincewm的博客-CSDN博客

2、ES整合商品上架

2.1、分析

es在整个项目中的应用:

1.对商品的全文检索功能

2.对日志的全文检索功能

为什么不用mysql?

es比mysql检索功能强大,并且对于庞大的检索数据,es性能更好,因为mysql是存在内存中的,而es可以分片存储。

需求:

  • 在后台选择上架的商品才能在网站展示
  • 上架的商品也可以被ES检索

2.2、创建sku的es索引库

2.2.1、两种索引库设计方案分析

索引库设计方案1(推荐,空间换时间):规格参数放在sku里

缺点:如果每个sku都存储规格参数(如尺寸),会有冗余存储,因为每个spu下面的sku规格参数都一样。例如spu“华为14Pro”下的sku“红色华为14Pro”,以及spu“华为手机”下的sku“蓝色华为14Pro”,这两个sku的规格参数“CPU:A14”相等。

{skuId:1spuId:11skyTitile:华为xxprice:999saleCount:99attr:[{尺寸:5},{CPU:高通945},{分辨率:全高清}]
}

索引库设计方案2(不推荐,传输的数据量大):规格参数和sku分离

sku索引
{spuId:1skuId:11
}
attr索引
{skuId:11attr:[{尺寸:5},{CPU:高通945},{分辨率:全高清}]
}

结论:如果将规格参数单独建立索引,会出现检索时出现大量数据传输的问题,会引起网络故障。

所以我们选方案一,用空间换时间

2.2.2、最终选用的索引库方案,nested类型

{ “type”: “keyword” }, 保持数据精度问题,可以检索,但不分词
“analyzer”: “ik_smart” 中文分词器
“index”: false, 不可被检索,不生成index
“doc_values”: false 默认为true,不可被聚合,es就不会维护一些聚合的信息

这个数据模型要先在es中建立

注意:

为了防止对象数组扁平化,商品属性字段类型设为nested类型。

es数组的扁平化处理:es存储对象数组时,它会将数组扁平化,也就是说将对象数组的每个属性抽取出来,作为一个数组。因此会出现查询紊乱的问题。

示例:下面user字段是对象数组类型,因为数组扁平化处理,下面结果跟期望查询结果不符:

PUT product
{"mappings":{"properties": {"skuId":{ "type": "long" },    #商品sku"spuId":{ "type": "keyword" },  #当前sku所属的spu。"skuTitle": {"type": "text","analyzer": "ik_smart"      #只有sku的标题需要被分词},"skuPrice": { "type": "keyword" },  "skuImg"  : { "type": "keyword" },  "saleCount":{ "type":"long" },"hasStock": { "type": "boolean" },    #是否有库存。在库存模块添加此商品库存后,此字段更为true"hotScore": { "type": "long"  },"brandId":  { "type": "long" },"catalogId": { "type": "long"  },"brandName": {"type": "keyword"}, "brandImg":{"type": "keyword","index": false,          #不可被检索"doc_values": false     #不可被聚合。doc_values默认为true},"catalogName": {"type": "keyword" }, "attrs": {"type": "nested",    #对象数组防止扁平化,不能用object类型"properties": {"attrId": {"type": "long"  },"attrName": {"type": "keyword","index": false,        #在后面“商城业务-检索服务”开发时这里要去掉"doc_values": false    #在后面“商城业务-检索服务”开发时这里要去掉},"attrValue": {"type": "keyword" }}}}}
}

2.3、SkuEsModel模型类

商品上架需要在es中保存spu信息并更新spu状态信息,所以我们就建立专门的vo来接收

SkuEsModel

写在common模块

@Data
public class SkuEsModel { //common中private Long skuId;private Long spuId;private String skuTitle;private BigDecimal skuPrice;private String skuImg;private Long saleCount;private boolean hasStock;private Long hotScore;private Long brandId;private Long catalogId;private String brandName;private String brandImg;private String catalogName;private List<Attr> attrs;@Datapublic static class Attr{private Long attrId;private String attrName;private String attrValue;}
}

2.4、【库存模块】库存量查询

查询sku列表是否有库存:

上架的话需要确定库存,所以调用ware微服务来检测是否有库存

@RestController
@RequestMapping("ware/waresku")
public class WareSkuController {@Autowiredprivate WareSkuService wareSkuService;@PostMapping(value = "/hasStock")public R getSkuHasStock(@RequestBody List<Long> skuIds) {List<SkuHasStockVo> vos = wareSkuService.getSkuHasStock(skuIds);return R.ok().setData(vos);}
}

实现也比较好理解,就是先用自定义的mapper查有没有库存

有的话,给库存赋值,并收集成集合

@Override
public List<SkuHasStockVo> getSkuHasStock(List<Long> skuIds) {List<SkuHasStockVo> skuHasStockVos = skuIds.stream().map(item -> {
//根据sku_id查库存,要写mapper,主要为了真实库存减去锁定库存Long count = this.baseMapper.getSkuStock(item);    SkuHasStockVo skuHasStockVo = new SkuHasStockVo();skuHasStockVo.setSkuId(item);skuHasStockVo.setHasStock(count == null ? false : count > 0);return skuHasStockVo;}).collect(Collectors.toList());return skuHasStockVos;}

自定义mapper

这里的库存并不是简单查一下库存表,需要自定义一个简单的sql。用库存减去锁定的库存即可得出!

<select id="getSkuStock" resultType="java.lang.Long">SELECT SUM(stock - stock_locked) FROM wms_ware_sku WHERE sku_id = #{skuId}
</select>

2.5、【查询模块】保存ES文档

2.5.1、常量类

public class EsConstant {//在es中的索引public static final String PRODUCT_INDEX = "gulimall_product";public static final Integer PRODUCT_PAGESIZE = 16;
}

2.5.2、controller

ElasticSaveController

package com.xxx.gulimall.search.controller;
@Slf4j
@RequestMapping(value = "/search/save")
@RestController
public class ElasticSaveController {@Autowiredprivate ProductSaveService productSaveService;/*** 上架商品* @param skuEsModels* @return*/@PostMapping(value = "/product")public R productStatusUp(@RequestBody List<SkuEsModel> skuEsModels) {boolean status=false;try {status = productSaveService.productStatusUp(skuEsModels);} catch (IOException e) {//log.error("商品上架错误{}",e);return R.error(BizCodeEnum.PRODUCT_UP_EXCEPTION.getCode(),BizCodeEnum.PRODUCT_UP_EXCEPTION.getMessage());}if(status){return R.error(BizCodeEnum.PRODUCT_UP_EXCEPTION.getCode(),BizCodeEnum.PRODUCT_UP_EXCEPTION.getMessage());}else {return R.ok();}}}

2.5.3、service

使用BulkRequest ,批量保存sku_es模型类列表到索引库

@Slf4j
@Service("productSaveService")
public class ProductSaveServiceImpl implements ProductSaveService {@Autowiredprivate RestHighLevelClient esRestClient;@Overridepublic boolean productStatusUp(List<SkuEsModel> skuEsModels) throws IOException {//1.在es中建立索引,建立号映射关系(doc/json/product-mapping.json)[kibana中执行product-mapping.txt,需要ES安装IK分词器]//2. 在ES中保存这些数据BulkRequest bulkRequest = new BulkRequest();for (SkuEsModel skuEsModel : skuEsModels) {//构造保存请求IndexRequest indexRequest = new IndexRequest(EsConstant.PRODUCT_INDEX);indexRequest.id(skuEsModel.getSkuId().toString());String jsonString = JSON.toJSONString(skuEsModel);indexRequest.source(jsonString, XContentType.JSON);bulkRequest.add(indexRequest);}BulkResponse bulk = esRestClient.bulk(bulkRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);//TODO 如果批量错误boolean hasFailures = bulk.hasFailures();List<String> collect = Arrays.asList(bulk.getItems()).stream().map(item -> {return item.getId();}).collect(Collectors.toList());log.info("商品上架完成:{}",collect);return hasFailures;}
}

2.6、【商品模块】上架单个spu

2.6.1、controller

SpuInfoController上架

/*** 商品上架*/
@PostMapping("/{spuId}/up")
public R spuUp(@PathVariable("spuId") Long spuId){spuInfoService.up(spuId);return R.ok();
}

2.6.2、远程调用库存模块

在商品模块的feign包下:

@FeignClient("gulimall-ware")
public interface WareFeignService {@PostMapping(value = "/ware/waresku/hasStock")R getSkuHasStock(@RequestBody List<Long> skuIds);}

商品模块启动类:

@EnableFeignClients(basePackages = "com.xunqi.gulimall.product.feign")

然后service里就能直接@Autowired注入了。

2.6.3、【公共模块】商品常量类,添加上传成功或失败的状态码

public class ProductConstant {public enum AttrEnum {ATTR_TYPE_BASE(1,"基本属性"),ATTR_TYPE_SALE(0,"销售属性");private int code;private String msg;public int getCode() {return code;}public String getMsg() {return msg;}AttrEnum(int code, String msg) {this.code = code;this.msg = msg;}}public enum ProductStatusEnum {NEW_SPU(0,"新建"),SPU_UP(1,"商品上架"),SPU_DOWN(2,"商品下架"),;private int code;private String msg;public int getCode() {return code;}public String getMsg() {return msg;}ProductStatusEnum(int code, String msg) {this.code = code;this.msg = msg;}}}

2.6.4、service

业务流程:

  1. 查询当前spu下的sku列表;
  2. 将这个sku列表封装成sku_es模型类列表;
    1. 给每个sku加上属性规格列表;
    2. 查询每个sku是否有库存,要远程调用库存模块;
    3. 给每个sku加上热度、所属品牌、所属分类名、所有可被检索的规格等属性;
  3. 将收集的sku_es模型类列表发给es保存,要远程调用查询模块。

SpuInfoServiceImpl

@Overridepublic void up(Long spuId) {//1.获得spu对应的sku集合List<SkuInfoEntity> skuInfoEntities = skuInfoService.getSkusBySpuId(spuId);//2.获得spu的基础属性实体集合List<ProductAttrValueEntity> baseAttrs = productAttrValueService.baseAttrListforSpu(spuId);//3.获得基本属性中可搜索的属性id//3.1获得spu基础属性实体集合中的属性id集合List<Long> attrIds = baseAttrs.stream().map(attr -> {return attr.getAttrId();}).collect(Collectors.toList());//3.2获得可搜索属性实体类对象List<Long> searchAttrIds = attrService.selectSearchAttrs(attrIds);//3.3将它们转化为set集合Set<Long> idSet = searchAttrIds.stream().collect(Collectors.toSet());//3.4对所有基础属性实体过滤,第一步是只保留可搜索属性实体类对象,第二步是给这些对象中的Attrs对象赋值,最后收集为attrsListList<SkuEsModel.Attrs> attrsList = baseAttrs.stream().filter(item -> {return idSet.contains(item.getAttrId());}).map(item -> {SkuEsModel.Attrs attrs = new SkuEsModel.Attrs();BeanUtils.copyProperties(item, attrs);return attrs;}).collect(Collectors.toList());//收集所有skuId的集合List<Long> skuIdList = skuInfoEntities.stream().map(SkuInfoEntity::getSkuId).collect(Collectors.toList());//TODO 1、发送远程调用,库存系统查询是否有库存Map<Long, Boolean> stockMap = null;try {R skuHasStock = wareFeignService.getSkuHasStock(skuIdList);TypeReference<List<SkuHasStockVo>> typeReference = new TypeReference<List<SkuHasStockVo>>() {};stockMap = skuHasStock.getData(typeReference).stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, item -> item.getHasStock()));} catch (Exception e) {log.error("库存服务查询异常:原因{}",e);}
//2、封装每个sku的信息Map<Long, Boolean> finalStockMap = stockMap;List<SkuEsModel> collect = skuInfoEntities.stream().map(sku -> {//组装需要的数据SkuEsModel esModel = new SkuEsModel();esModel.setSkuPrice(sku.getPrice());esModel.setSkuImg(sku.getSkuDefaultImg());//设置库存信息if (finalStockMap == null) {esModel.setHasStock(true);} else {esModel.setHasStock(finalStockMap.get(sku.getSkuId()));}//TODO 2、热度评分。0esModel.setHotScore(0L);//TODO 3、查询品牌和分类的名字信息BrandEntity brandEntity = brandService.getById(sku.getBrandId());esModel.setBrandName(brandEntity.getName());esModel.setBrandId(brandEntity.getBrandId());esModel.setBrandImg(brandEntity.getLogo());CategoryEntity categoryEntity = categoryService.getById(sku.getCatalogId());esModel.setCatalogId(categoryEntity.getCatId());esModel.setCatalogName(categoryEntity.getName());//设置检索属性esModel.setAttrs(attrsList);BeanUtils.copyProperties(sku,esModel);return esModel;}).collect(Collectors.toList());//TODO 5、将数据发给es进行保存:mall-searchR r = searchFeignService.productStatusUp(collect);if (r.getCode() == 0) {//远程调用成功//TODO 6、修改当前spu的状态,具体代码看代码块后面SpuInfoDao.xmlthis.baseMapper.updaSpuStatus(spuId, ProductConstant.ProductStatusEnum.SPU_UP.getCode());} else {//远程调用失败//TODO 7、重复调用?接口幂等性:重试机制}}

SpuInfoDao更新spu状态

    <update id="updaSpuStatus">UPDATE pms_spu_info SET publish_status = #{code} ,update_time = NOW() WHERE id = #{spuId}</update>

2.6.5、测试

商品上架用到了三个微服务,分别是product、ware、search

那我们分别debug启动它们,然后在这些微服务中使用的方法中打上断点,查看调用流程

获得spu对应的sku集合

获得spu的基础属性实体集合

基础属性如下:

给SkuEsModel.Attrs对象赋值

测试

2.6.6、修改结果类R

 public <T> T getData(String key, TypeReference<T> typeReference) {Object data = get(key);// 默认是map类型,springmvc做的String jsonStr = JSON.toJSONString(data);T t = JSON.parseObject(jsonStr, typeReference);return t;}// 利用fastJson进行逆转// 这里要声明泛型<T>,这个泛型只跟方法有关,跟类无关。// 例如类上有个泛型,这里可以使用类上的泛型,就不用声明public <T> T getData(TypeReference<T> typeReference) {Object data = get("data");// 默认是map类型,springmvc做的String jsonStr = JSON.toJSONString(data);T t = JSON.parseObject(jsonStr, typeReference);return t;}public R setData(Object data) {put("data", data);return this;}

谷粒商城笔记+踩坑(9)——上架商品spu到ES索引库相关推荐

  1. 谷粒商城笔记+踩坑(6)——商品服务-属性及其关联分组

      导航: 谷粒商城笔记+踩坑汇总篇_谷粒商城笔记踩坑6_vincewm的博客-CSDN博客 目录 10.商品服务-属性(规格参数和销售属性) 10.1.新增属性时,新增属性和属性分组的关联关系 10 ...

  2. 谷粒商城笔记+踩坑(15)——商品详情搭建+异步编排

    导航: 谷粒商城笔记+踩坑汇总篇 目录 1.搭建页面环境 1.1.配置 Nginx 和 网关 1.2.动静资源配置 1.3.搜索页到详情页跳转 2.模型类抽取和controller 2.1.分析首页需 ...

  3. 谷粒商城笔记+踩坑(17)——【认证模块】登录,用户名密码登录+微博社交登录+SpringSession+xxl-sso单点登录

    导航: 谷粒商城笔记+踩坑汇总篇 目录 5. 用户名密码登录 5.1[认证模块]登录业务 5.1.1 模型类,接收用户名密码 5.1.2 feign客户端新增登录功能 5.1.3 LoginContr ...

  4. 谷粒商城笔记+踩坑(18)——购物车

    导航: 谷粒商城笔记+踩坑汇总篇 目录 一.环境搭建 1.1.购物车模块初始化 1.2.动静资源处理 1.3.页面跳转配置 二.数据模型分析 2.1.购物车需求 2.1.1.离线购物车和在线购物车需求 ...

  5. 谷粒商城笔记+踩坑(1)——架构、项目环境搭建、代码生成器

     导航: 谷粒商城笔记+踩坑汇总篇_谷粒商城笔记踩坑6_vincewm的博客-CSDN博客 目录 1.项目介绍 1.1 微服务架构图 1.2. 微服务划分图 2.项目环境搭建 2.1. 虚拟机搭建环境 ...

  6. 谷粒商城笔记+踩坑(19)——订单模块构建、登录拦截器

    导航: 谷粒商城笔记+踩坑汇总篇 目录 1.页面环境搭建 1.1 动静分离 1.2 hosts添加域名映射 1.3 配置网关和nacos 1.4 引导类开启注册发现和feign客户端 1.5 thym ...

  7. 谷粒商城笔记+踩坑(22)——库存自动解锁。RabbitMQ延迟队列

    导航: 谷粒商城笔记+踩坑汇总篇 目录 1 业务流程,订单失败后自动回滚解锁库存 可靠消息+最终一致性方案 2[仓库服务]RabbitMQ环境准备 2.1 导入依赖 2.2 yml配置RabbitMQ ...

  8. 谷粒商城笔记+踩坑(23)——定时关闭订单

    导航: 谷粒商城笔记+踩坑汇总篇 目录 1.定时关单 1.0.业务流程 1.1.创建交换机.队列以及之间的绑定 1.2.在订单创建成功时向MQ中 延时队列发送消息 1.3.在订单的关闭之后时向MQ发送 ...

  9. 谷粒商城开发踩坑及部分知识点大总结

    谷粒商城开发BUG踩坑及部分知识点大总结 基本上bug的出现位置和时间线都能够匹配 如果对你有帮助的话就点个赞哈 2022.6.28 github设置ssh免密登陆,以下代码在git bash上面输入 ...

最新文章

  1. Spring MVC 完整示例
  2. 两种过年烟花,你喜欢哪一种(HTML+CSS+JS)
  3. Xamarin.Android 引导页
  4. 23.PHP的哈希表实现
  5. 墙面有几种装修方法_卧室装修静音环保攻略,赶紧收藏起
  6. MFC界面设计入门篇
  7. 什么是SWFObject?
  8. log算子 和dog 算子
  9. java十进制二进制之间的互相转换
  10. python qq模块_用python写一个QQ机器人
  11. Unity3D FPS 第一人称视角移动
  12. 装了伽卡他卡打不开任务管理器的解决办法
  13. 学计算机专业可以做施工员吗,大龄转行做工程施工员,学起吃力吗?
  14. Contelec KL750-5K0/M-SE醉后不知天在水
  15. 第7章第26节:三图排版:三张图片并列排版 [PowerPoint精美幻灯片实战教程]
  16. js使用广度优先给树形结构添加level
  17. 联合利华投资10亿欧元,致力到2030年淘汰清洁产品中的化石燃料
  18. JS逆向之去哪儿旅行 - - - 动态混淆
  19. XDOJ魔王语言解释
  20. 福布斯2020年度AI大奖

热门文章

  1. Kafka淘汰倒计时!这个云原生消息中间件,腾讯、华为都用疯了?
  2. 【burpsuite安全练兵场-服务端7】访问控制漏洞和权限提升-11个实验(全)
  3. 【Linux 】各目录及每个目录的详细介绍
  4. C语言中函数名的意义深究
  5. 湿台清洗中颗粒去除的新概念
  6. 如何调整显示器来获得更舒服的体验
  7. Java国际化的登录页面
  8. 麦块服务器怎么注册的视频,我的世界麦块服务器怎么注册密码
  9. iphone4 同步联系人
  10. JAVA中直接执行sql语句示例