一、前提

ES作为全文检索引擎

  1. 承担项目中的所有全文检索功能。

    比如京东商城手机检索功能:按照名称或者不同规格属性进行检索

  2. 承担日志的全文检索功能,ELK。

    E:ElasticSearch,存储、分析

    L:LogStash,收集日志,并存储在ES中

    K:Kibana,可视化界面

二、商品上架

上架的商品才可以在网站展示。

上架的商品需要可以被检索。

2.1. 商品Mapping

分析:商品上架在 es 中是存 sku 还是 spu?

  1. 检索的时候输入名字,是需要按照 sku 的 title 进行全文检索的
  2. 检索使用商品规格,规格是 spu 的公共属性,每个 spu 是一样的
  3. 按照分类 id 进去的都是直接列出 spu 的,还可以切换。
  4. 我们如果将 sku 的全量信息保存到 es 中(包括 spu 属性)就太多量字段了。
  5. 我们如果将 spu 以及他包含的 sku 信息保存到 es 中,也可以方便检索。但是 sku 属于spu 的级联对象,在 es 中需要 nested 模型,这种性能差点。
  6. 但是存储与检索我们必须性能折中。
  7. 如果我们分拆存储,spu 和 attr 一个索引,sku 单独一个索引可能涉及的问题

检索商品的名字,如“手机”,对应的 spu 有很多,我们要分析出这些 spu 的所有关联属性, 再做一次查询,就必须将所有 spu_id 都发出去。假设有 1 万个数据,数据传输一次就

10000*4=4MB;并发情况下假设 1000 检索请求,那就是 4GB 的数据,,传输阻塞时间会很长,业务更加无法继续。

所以,我们如下设计,这样才是文档区别于关系型数据库的地方,宽表设计,不能去考虑数据库范式。

PUT product——product 的 mapping

PUT product
{"mappings":{"properties":{"skuId":{"type":"long"},"spuId":{"type":"keyword"},"skuTitle":{"type":"text","analyzer":"ik_smart"},"skuPrice":{"type":"keyword"},"skuImg":{"type":"keyword","index":false,"doc_values":false},"saleCount":{"type":"long"},"hasStock":{"type":"boolean"},"hotScore":{"type":"long"},"brandId":{"type":"long"},"catalogId":{"type":"long"},"brandName":{"type":"keyword","index":false,"doc_values":false},"brandImg":{"type":"keyword","index":false,"doc_values":false},"catalogName":{"type":"keyword","index":false,"doc_values":false},"attrs":{"type":"nested","properties":{"attrId":{"type":"long"},"attrName":{"type":"keyword","index":false,"doc_values":false},"attrValue":{"type":"keyword"}}}}}
}

index

默认 true,如果为 false,表示该字段不会被索引,但是检索结果里面有,但字段本身不能当做检索条件。

doc_values

默认 true,设置为 false,表示不可以做排序、聚合以及脚本操作,这样更节省磁盘空间。

还可以通过设定 doc_values 为 true,index 为 false 来让字段不能被搜索但可以用于排序、聚合以及脚本操作。

2.2. 上架细节

上架是将后台的商品放在 es 中可以提供检索和查询功能

  1. hasStock:代表是否有库存。默认上架的商品都有库存。如果库存无货的时候才需要更新一下 es
  2. 库存补上以后,也需要重新更新一下 es
  3. hotScore 是热度值,我们只模拟使用点击率更新热度。点击率增加到一定程度才更新热度值。
  4. 下架就是从 es 中移除检索项,以及修改 mysql 状态

商品上架步骤:

  1. 先在 es 中按照之前的 mapping 信息,建立 product 索引。
  2. 点击上架,查询出所有 sku 的信息,保存到 es 中
  3. es 保存成功返回,更新数据库的上架状态信息。

2.3. 数据一致性

  • 商品无库存的时候需要更新 es 的库存信息
  • 商品有库存也要更新 es 的信息

三、商品检索

3.1. 检索业务分析

商品检索的三个入口:

  1. 选择分类进入商品检索

  2. 输入检索关键字展示检索页

  3. 选择筛选条件进入

删选条件&排序条件

  • 全文检索:skuTitle
  • 排序:saleCount、hotScore、skuPrice
  • 过滤:hasStock、skuPrice 区间、brandId、catalogId、attrs
  • 聚合:attrs

完整的 url 参数

keyword=小米&sort=saleCount_desc/asc&hasStock=0/1&skuPrice=400_1900&brandId=1
&catalogId=1&attrs=1_3G:4G:5G&attrs=2_骁龙 845&attrs=4_高清屏

3.2. 检索语句构建

3.2.1. 请求参数模型

@Data
public class SearchParam {private String keyword;//页面传递过来的全文匹配关键字 vprivate Long catalog3Id;//三级分类id v/*** sort=saleCount_asc/desc* sort=skuPrice_asc/desc* sort=hotScore_asc/desc*/private String sort;//排序条件 v/*** 过滤条件* hasStock(是否有货)、skuPrice 区间、brandId、catalog3Id、attrs* hasStock=0/1* skuPrice=1_500/_500/500_* brandId=1* attrs=2_5 存:6 寸*/private Integer hasStock;//是否只显示有货   v  0(无库存)1(有库存)private String skuPrice;//价格区间查询 vprivate List<Long> brandId;//按照品牌进行查询,可以多选 vprivate List<String> attrs;//按照属性进行筛选 vprivate Integer pageNum = 1;//页码private String _queryString;//原生的所有查询条件
}

3.2.2. 构建参数

/*** 准备检索请求* #模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存),排序,分页,高亮,聚合分析** @return*/
private SearchRequest buildSearchRequrest(SearchParam param) {SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();//构建DSL语句的/*** 查询:过滤(按照属性,分类,品牌,价格区间,库存)*///1、构建bool - queryBoolQueryBuilder boolQuery = QueryBuilders.boolQuery();//1.1、must-模糊匹配,if (!StringUtils.isEmpty(param.getKeyword())) {boolQuery.must(QueryBuilders.matchQuery("skuTitle", param.getKeyword()));}//1.2、bool - filter - 按照三级分类id查询if (param.getCatalog3Id() != null) {boolQuery.filter(QueryBuilders.termQuery("catalogId", param.getCatalog3Id()));}//1.2、bool - filter - 按照品牌id查询if (param.getBrandId() != null && param.getBrandId().size() > 0) {boolQuery.filter(QueryBuilders.termsQuery("brandId", param.getBrandId()));}//1.2、bool - filter - 按照所有指定的属性进行查询if (param.getAttrs() != null && param.getAttrs().size() > 0) {for (String attrStr : param.getAttrs()) {//attrs=1_5寸:8寸&attrs=2_16G:8GBoolQueryBuilder nestedboolQuery = QueryBuilders.boolQuery();//attr = 1_5寸:8寸String[] s = attrStr.split("_");String attrId = s[0]; //检索的属性idString[] attrValues = s[1].split(":"); //这个属性的检索用的值nestedboolQuery.must(QueryBuilders.termQuery("attrs.attrId", attrId));nestedboolQuery.must(QueryBuilders.termsQuery("attrs.attrValue", attrValues));//每一个必须都得生成一个nested查询NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedboolQuery, ScoreMode.None);boolQuery.filter(nestedQuery);}}//1.2、bool - filter - 按照库存是否有进行查询if(param.getHasStock() != null){boolQuery.filter(QueryBuilders.termQuery("hasStock", param.getHasStock() == 1));}//1.2、bool - filter - 按照价格区间if (!StringUtils.isEmpty(param.getSkuPrice())) {//1_500/_500/500_/*** "range": {*             "skuPrice": {*               "gte": 0,*               "lte": 6000*             }*           }*/RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");String[] s = param.getSkuPrice().split("_");if (s.length == 2) {//区间rangeQuery.gte(s[0]).lte(s[1]);} else if (s.length == 1) {if (param.getSkuPrice().startsWith("_")) {rangeQuery.lte(s[0]);}if (param.getSkuPrice().endsWith("_")) {rangeQuery.gte(s[0]);}}boolQuery.filter(rangeQuery);}//把以前的所有条件都拿来进行封装sourceBuilder.query(boolQuery);/*** 排序,分页,高亮,*///2.1、排序if (!StringUtils.isEmpty(param.getSort())) {String sort = param.getSort();//sort=hotScore_asc/descString[] s = sort.split("_");SortOrder order = s[1].equalsIgnoreCase("asc") ? SortOrder.ASC : SortOrder.DESC;sourceBuilder.sort(s[0], order);}//2.2、分页  pageSize:5//  pageNum:1  from:0  size:5  [0,1,2,3,4]// pageNum:2  from:5   size:5//from = (pageNum-1)*sizesourceBuilder.from((param.getPageNum() - 1) * EsConstant.PRODUCT_PAGESIZE);sourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);//2.3、高亮if (!StringUtils.isEmpty(param.getKeyword())) {HighlightBuilder builder = new HighlightBuilder();builder.field("skuTitle");builder.preTags("<b style='color:red'>");builder.postTags("</b>");sourceBuilder.highlighter(builder);}/*** 聚合分析*///1、品牌聚合TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg");brand_agg.field("brandId").size(50);//品牌聚合的子聚合brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(1));brand_agg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(1));//TODO 1、聚合brandsourceBuilder.aggregation(brand_agg);//2、分类聚合 catalog_aggTermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg").field("catalogId").size(20);catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));//TODO 2、聚合catalogsourceBuilder.aggregation(catalog_agg);//3、属性聚合 attr_aggNestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");//聚合出当前所有的attrIdTermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId");//聚合分析出当前attr_id对应的名字attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));//聚合分析出当前attr_id对应的所有可能的属性值attrValueattr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));attr_agg.subAggregation(attr_id_agg);//TODO 3、聚合attrsourceBuilder.aggregation(attr_agg);String s = sourceBuilder.toString();System.out.println("构建的DSL" + s);SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, sourceBuilder);return searchRequest;
}

3.3. 结果提取封装

3.3.1. 响应数据模型

package com.atguigu.gulimall.search.vo;import com.atguigu.common.to.es.SkuEsModel;
import lombok.Data;import java.util.ArrayList;
import java.util.List;@Data
public class SearchResult {//查询到的所有商品信息private List<SkuEsModel> products;/*** 以下是分页信息*/private Integer pageNum;//当前页码private Long total;//总记录数private Integer totalPages;//总页码private List<Integer> pageNavs;private List<BrandVo> brands;//当前查询到的结果,所有涉及到的品牌private List<CatalogVo> catalogs;//当前查询到的结果,所有涉及到的所有分类private List<AttrVo> attrs;//当前查询到的结果,所有涉及到的所有属性//==========以上是返回给页面的所有信息============//面包屑导航数据private List<NavVo> navs = new ArrayList<>();private List<Long> attrIds = new ArrayList<>();@Datapublic static class NavVo{private String navName;private String navValue;private String link;}@Datapublic static class BrandVo{private Long brandId;private String brandName;private String brandImg;}@Datapublic static class CatalogVo{private Long catalogId;private String catalogName;}@Datapublic static class AttrVo{private Long attrId;private String attrName;private List<String> attrValue;}
}

3.3.2. 响应结果封装

/*** 构建结果数据** @param response** @return*/
private SearchResult buildSearchResult(SearchResponse response, SearchParam param) {SearchResult result = new SearchResult();//1、返回的所有查询到的商品SearchHits hits = response.getHits();List<SkuEsModel> esModels = new ArrayList<>();if (hits.getHits() != null && hits.getHits().length > 0) {for (SearchHit hit : hits.getHits()) {String sourceAsString = hit.getSourceAsString();SkuEsModel esModel = JSON.parseObject(sourceAsString, SkuEsModel.class);if(!StringUtils.isEmpty(param.getKeyword())){HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");String string = skuTitle.getFragments()[0].string();esModel.setSkuTitle(string);}esModels.add(esModel);};}result.setProducts(esModels);
//        //2、当前所有商品涉及到的所有属性信息List<SearchResult.AttrVo> attrVos = new ArrayList<>();ParsedNested attr_agg = response.getAggregations().get("attr_agg");ParsedLongTerms attr_id_agg = attr_agg.getAggregations().get("attr_id_agg");for (Terms.Bucket bucket : attr_id_agg.getBuckets()) {SearchResult.AttrVo attrVo = new SearchResult.AttrVo();//1、得到属性的idlong attrId = bucket.getKeyAsNumber().longValue();//2、得到属性的名字String attrName = ((ParsedStringTerms) bucket.getAggregations().get("attr_name_agg")).getBuckets().get(0).getKeyAsString();//3、得到属性的所有值List<String> attrValues = ((ParsedStringTerms) bucket.getAggregations().get("attr_value_agg")).getBuckets().stream().map(item -> {String keyAsString = ((Terms.Bucket) item).getKeyAsString();return keyAsString;}).collect(Collectors.toList());attrVo.setAttrId(attrId);attrVo.setAttrName(attrName);attrVo.setAttrValue(attrValues);attrVos.add(attrVo);}result.setAttrs(attrVos);
//        //3、当前所有商品涉及到的所有品牌信息List<SearchResult.BrandVo> brandVos = new ArrayList<>();ParsedLongTerms brand_agg = response.getAggregations().get("brand_agg");for (Terms.Bucket bucket : brand_agg.getBuckets()) {SearchResult.BrandVo brandVo = new SearchResult.BrandVo();//1、得到品牌的idlong brandId = bucket.getKeyAsNumber().longValue();//2、得到品牌的名String brandName = ((ParsedStringTerms) bucket.getAggregations().get("brand_name_agg")).getBuckets().get(0).getKeyAsString();//3、得到品牌的图片String brandImg = ((ParsedStringTerms) bucket.getAggregations().get("brand_img_agg")).getBuckets().get(0).getKeyAsString();brandVo.setBrandId(brandId);brandVo.setBrandName(brandName);brandVo.setBrandImg(brandImg);brandVos.add(brandVo);}result.setBrands(brandVos);
//        //4、当前所有商品涉及到的所有分类信息ParsedLongTerms catalog_agg = response.getAggregations().get("catalog_agg");List<SearchResult.CatalogVo> catalogVos = new ArrayList<>();List<? extends Terms.Bucket> buckets = catalog_agg.getBuckets();for (Terms.Bucket bucket : buckets) {SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();//得到分类idString keyAsString = bucket.getKeyAsString();catalogVo.setCatalogId(Long.parseLong(keyAsString));//得到分类名ParsedStringTerms catalog_name_agg = bucket.getAggregations().get("catalog_name_agg");String catalog_name = catalog_name_agg.getBuckets().get(0).getKeyAsString();catalogVo.setCatalogName(catalog_name);catalogVos.add(catalogVo);}result.setCatalogs(catalogVos);
//        ========以上从聚合信息中获取======
//        //5、分页信息-页码result.setPageNum(param.getPageNum());
//        //5、分页信息-总记录树long total = hits.getTotalHits().value;result.setTotal(total);
//        //5、分页信息-总页码-计算  11/2 = 5 .. 1int totalPages = (int) total % EsConstant.PRODUCT_PAGESIZE == 0 ? (int) total / EsConstant.PRODUCT_PAGESIZE : ((int) total / EsConstant.PRODUCT_PAGESIZE + 1);result.setTotalPages(totalPages);List<Integer> pageNavs = new ArrayList<>();for (int i=1;i<=totalPages;i++){pageNavs.add(i);}result.setPageNavs(pageNavs);//6、构建面包屑导航功能if(param.getAttrs()!=null && param.getAttrs().size()>0){List<SearchResult.NavVo> collect = param.getAttrs().stream().map(attr -> {//1、分析每个attrs传过来的查询参数值。SearchResult.NavVo navVo = new SearchResult.NavVo();
//            attrs=2_5存:6寸String[] s = attr.split("_");navVo.setNavValue(s[1]);R r = productFeignService.attrInfo(Long.parseLong(s[0]));result.getAttrIds().add(Long.parseLong(s[0]));if(r.getCode() == 0){AttrResponseVo data = r.getData("attr", new TypeReference<AttrResponseVo>() {});navVo.setNavName( data.getAttrName());}else{navVo.setNavName(s[0]);}
////2、取消了这个面包屑以后,我们要跳转到那个地方.将请求地址的url里面的当前置空//拿到所有的查询条件,去掉当前。//attrs=  15_海思(Hisilicon)String replace = replaceQueryString(param, attr,"attrs");navVo.setLink("http://search.gulimall.com/list.html?"+replace);return navVo;}).collect(Collectors.toList());result.setNavs(collect);}//品牌,分类if(param.getBrandId()!=null && param.getBrandId().size()>0){List<SearchResult.NavVo> navs = result.getNavs();SearchResult.NavVo navVo = new SearchResult.NavVo();navVo.setNavName("品牌");//TODO 远程查询所有品牌R r = productFeignService.brandsInfo(param.getBrandId());if(r.getCode() == 0){List<BrandVo> brand = r.getData("brand", new TypeReference<List<BrandVo>>() {});StringBuffer buffer = new StringBuffer();String replace = "";for (BrandVo brandVo : brand) {buffer.append(brandVo.getBrandName()+";");replace = replaceQueryString(param, brandVo.getBrandId()+"","brandId");}navVo.setNavValue(buffer.toString());navVo.setLink("http://search.gulimall.com/list.html?"+replace);}navs.add(navVo);}//TODO 分类:不需要导航取消return result;
}

谷粒商城:15.商城业务 — 商品上架相关推荐

  1. 【商城报错】-商品上架功能报错

    1.调用远程库存服务报错 空指针异常 调用远程库存服务,返回的数据,是给R加泛型,设置Data,然后发现R是继承了HashMap,写自己的私有属性是封装不进去的[继承了HashMap的实体类的私有属性 ...

  2. gulimall-商城业务-商品上架

    商城业务 前言 一.商品上架 1.1 商品 Mapping 1.2 商品信息保存到es 1.3 es数组的扁平化处理 1.4 构造基本数据 前言 本文继续记录B站谷粒商城项目视频 P128-135 的 ...

  3. 16.商品业务-商品上架

    文章目录 1 sku在es中的存储模型分析 1.1 商品Mapping 2 nested数据类型 3 商品上架服务 3.1 远程调用查询库存服务 3.2 实现es存储业务 3.2.1 远程调用接口 3 ...

  4. 谷粒商城-商城业务(商品上架)

    商品上架 在商城中搜索商品,只能搜索到已上架的商品. 而商品上架时,需要把数据也同步到elasticsearch中以供搜索. 但是肯定不能把完整的数据全部存到es中,因为es中的数据是存储在内存中的, ...

  5. 【谷粒商城高级篇】商品服务 商品上架

    谷粒商城笔记合集 分布式基础篇 分布式高级篇 高可用集群篇 ===简介&环境搭建=== ===Elasticsearch=== 项目简介与分布式概念(第一.二章) Elasticsearch: ...

  6. 谷粒商城项目8——商品上架 上架商品sku保存到es nginx配置

    文章目录 一.商城业务 1.商品上架 1.1 ES 的存储结构分析 1.2 PUT product 1.3 一些细节 2.商品上架-构造基本数据 3.商品上架-业务代码: 4.商品上架-search模 ...

  7. 谷粒商城十elasticsearch搜索服务及商品上架

    springboot整合Elasticsearch-Rest-Client <?xml version="1.0" encoding="UTF-8"?&g ...

  8. 谷粒商城高级篇(36)——商品上架之上传数据到Elasticsearch

    商品上架之上传数据到Elasticsearch 功能需求分析 分析-怎么设计存储结构来保存数据 空间换时间 时间换空间 最终方案-存储结构 关于 nested 类型 商品上架功能实现 guimall- ...

  9. 谷粒商城(商品上架、首页、异步、商品详细)思路详解

    商品业务总结 1.商品上架 1.sku模型分析 2.嵌入式 3.构造基本数据 4.检索属性 5.设置库存信息 6.保存model到es中 7.R的修改 2.首页 1.新建页面 2.获取一级目录. 3. ...

  10. 谷粒商城--整合Elasticsearch和商品的上架

    整合Elasticsearch和商品的上架 一.整合ES ES常用概念 索引,类型,文档是什么? 倒排索引 相关度分数score的计算 安装ES和Kibana 快速安装 ES kibana 初步检索_ ...

最新文章

  1. 删除a3.txt文件中含dong的行
  2. Kali Linux 64位架构安装Veil-Evasion
  3. 解决Dialog 消失,输入法不消失的问题
  4. Centos安装、配置nginx
  5. 临床必备 | 第 5 期全基因组/外显子组家系分析理论和实战
  6. 网上招生报名系统V1.0发布
  7. Redis学习笔记(四)——数据结构之List
  8. SPSS之多因素方差分析
  9. 读者福利,单独赠书啦!这次的书你肯定喜欢!
  10. 使用福禄克CFP光纤测试仪进行Tier 1和Tier 2光纤测试
  11. orcal添加序列让主键的自动增长
  12. 北斗在线app服务器,北斗卫星导航app,北斗卫星导航app官网手机版预约 v1.0 - 浏览器家园...
  13. 剑指 Offer 05. 替换空格(完整代码)
  14. HyperV使用主机摄像头
  15. 青龙面板跑爱企查脚本 兑换爱奇艺月卡 百度网盘会员等
  16. 模板:求图的强连通分量(SCC)
  17. 量子计算机能为我们做什么,为实现量子计算,我们还需要做些什么
  18. 基于arduino的智能家居系统
  19. CMS-CMS框架解析
  20. 007-安装百度云,搜狗输入法,播放器

热门文章

  1. 20. 远程端口查看
  2. 7.Magento系统配置(System.xml)
  3. magento xml配置详解(1)
  4. mac os 使用记录
  5. StringBuilder类的作用,以及与String类的相互转换
  6. go语言的特殊变量 iota
  7. 2018.07.09 顺序对齐(线性dp)
  8. 用shell查找某目录下的最大文件
  9. Windows Azure 安全最佳实践 - 第 7 部分:提示、工具和编码最佳实践
  10. 反编译那些事儿(三)—那些看似没用到的全局变量和那些使用了动态参数的方法