谷粒商城:15.商城业务 — 商品上架
一、前提
ES作为全文检索引擎
承担项目中的所有全文检索功能。
比如京东商城手机检索功能:按照名称或者不同规格属性进行检索
承担日志的全文检索功能,ELK。
E:ElasticSearch,存储、分析
L:LogStash,收集日志,并存储在ES中
K:Kibana,可视化界面
二、商品上架
上架的商品才可以在网站展示。
上架的商品需要可以被检索。
2.1. 商品Mapping
分析:商品上架在 es 中是存 sku 还是 spu?
- 检索的时候输入名字,是需要按照 sku 的 title 进行全文检索的
- 检索使用商品规格,规格是 spu 的公共属性,每个 spu 是一样的
- 按照分类 id 进去的都是直接列出 spu 的,还可以切换。
- 我们如果将 sku 的全量信息保存到 es 中(包括 spu 属性)就太多量字段了。
- 我们如果将 spu 以及他包含的 sku 信息保存到 es 中,也可以方便检索。但是 sku 属于spu 的级联对象,在 es 中需要 nested 模型,这种性能差点。
- 但是存储与检索我们必须性能折中。
- 如果我们分拆存储,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 中可以提供检索和查询功能
- hasStock:代表是否有库存。默认上架的商品都有库存。如果库存无货的时候才需要更新一下 es
- 库存补上以后,也需要重新更新一下 es
- hotScore 是热度值,我们只模拟使用点击率更新热度。点击率增加到一定程度才更新热度值。
- 下架就是从 es 中移除检索项,以及修改 mysql 状态
商品上架步骤:
- 先在 es 中按照之前的 mapping 信息,建立 product 索引。
- 点击上架,查询出所有 sku 的信息,保存到 es 中
- es 保存成功返回,更新数据库的上架状态信息。
2.3. 数据一致性
- 商品无库存的时候需要更新 es 的库存信息
- 商品有库存也要更新 es 的信息
三、商品检索
3.1. 检索业务分析
商品检索的三个入口:
选择分类进入商品检索
输入检索关键字展示检索页
选择筛选条件进入
删选条件&排序条件
- 全文检索: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.调用远程库存服务报错 空指针异常 调用远程库存服务,返回的数据,是给R加泛型,设置Data,然后发现R是继承了HashMap,写自己的私有属性是封装不进去的[继承了HashMap的实体类的私有属性 ...
- gulimall-商城业务-商品上架
商城业务 前言 一.商品上架 1.1 商品 Mapping 1.2 商品信息保存到es 1.3 es数组的扁平化处理 1.4 构造基本数据 前言 本文继续记录B站谷粒商城项目视频 P128-135 的 ...
- 16.商品业务-商品上架
文章目录 1 sku在es中的存储模型分析 1.1 商品Mapping 2 nested数据类型 3 商品上架服务 3.1 远程调用查询库存服务 3.2 实现es存储业务 3.2.1 远程调用接口 3 ...
- 谷粒商城-商城业务(商品上架)
商品上架 在商城中搜索商品,只能搜索到已上架的商品. 而商品上架时,需要把数据也同步到elasticsearch中以供搜索. 但是肯定不能把完整的数据全部存到es中,因为es中的数据是存储在内存中的, ...
- 【谷粒商城高级篇】商品服务 商品上架
谷粒商城笔记合集 分布式基础篇 分布式高级篇 高可用集群篇 ===简介&环境搭建=== ===Elasticsearch=== 项目简介与分布式概念(第一.二章) Elasticsearch: ...
- 谷粒商城项目8——商品上架 上架商品sku保存到es nginx配置
文章目录 一.商城业务 1.商品上架 1.1 ES 的存储结构分析 1.2 PUT product 1.3 一些细节 2.商品上架-构造基本数据 3.商品上架-业务代码: 4.商品上架-search模 ...
- 谷粒商城十elasticsearch搜索服务及商品上架
springboot整合Elasticsearch-Rest-Client <?xml version="1.0" encoding="UTF-8"?&g ...
- 谷粒商城高级篇(36)——商品上架之上传数据到Elasticsearch
商品上架之上传数据到Elasticsearch 功能需求分析 分析-怎么设计存储结构来保存数据 空间换时间 时间换空间 最终方案-存储结构 关于 nested 类型 商品上架功能实现 guimall- ...
- 谷粒商城(商品上架、首页、异步、商品详细)思路详解
商品业务总结 1.商品上架 1.sku模型分析 2.嵌入式 3.构造基本数据 4.检索属性 5.设置库存信息 6.保存model到es中 7.R的修改 2.首页 1.新建页面 2.获取一级目录. 3. ...
- 谷粒商城--整合Elasticsearch和商品的上架
整合Elasticsearch和商品的上架 一.整合ES ES常用概念 索引,类型,文档是什么? 倒排索引 相关度分数score的计算 安装ES和Kibana 快速安装 ES kibana 初步检索_ ...
最新文章
- 删除a3.txt文件中含dong的行
- Kali Linux 64位架构安装Veil-Evasion
- 解决Dialog 消失,输入法不消失的问题
- Centos安装、配置nginx
- 临床必备 | 第 5 期全基因组/外显子组家系分析理论和实战
- 网上招生报名系统V1.0发布
- Redis学习笔记(四)——数据结构之List
- SPSS之多因素方差分析
- 读者福利,单独赠书啦!这次的书你肯定喜欢!
- 使用福禄克CFP光纤测试仪进行Tier 1和Tier 2光纤测试
- orcal添加序列让主键的自动增长
- 北斗在线app服务器,北斗卫星导航app,北斗卫星导航app官网手机版预约 v1.0 - 浏览器家园...
- 剑指 Offer 05. 替换空格(完整代码)
- HyperV使用主机摄像头
- 青龙面板跑爱企查脚本 兑换爱奇艺月卡 百度网盘会员等
- 模板:求图的强连通分量(SCC)
- 量子计算机能为我们做什么,为实现量子计算,我们还需要做些什么
- 基于arduino的智能家居系统
- CMS-CMS框架解析
- 007-安装百度云,搜狗输入法,播放器