分布式高级篇(四) - 商城业务 - 检索服务
检索服务
检索服务基础环境搭建
-
最终效果
-
效果演示
- 1、全文检索:skuTitle -> keyword
- 2、排序:saleCount(销量)、hotScore(热度评分)、skuPrice(价格)
- 3、过滤:hasStock(是否有货)、skuPrice(价格区间)、brandId(品牌id 可以多选)、catalogId
- 4、聚合:attrs(属性)
- 5、面包屑导航
- 6、条件删除&条件筛选联动
搭建页面环境
-
第一步:search微服务导入依赖
<!--模板引擎 thymeleaf--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!--dev-tools--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency>
将检索页面
index.html
,拷贝进项目,放入templates
目录下 -
第二步:修改本地Hosts文件
目的使 search.mall.com 生效,访问search.mall.com相当于直接访问 nginx
C:WindowsSystem32driversetc
修改成功后,保存hosts,页面访问
search.mall.com
查看效果(确报nginx容器启动) -
第三步:将页面所需的静态资源拷贝进nginx中的
html/static/search
目录下(利用xshell和xftp)修改nginx 转发配置 在
nginx/conf/conf.d
目录下的 mall.conf,只要是以mall.com结尾的请求地址,匹配上/static/,都会请求nginx下的静态资源。修改完成 保存退出并重启nginx容器(docker restart nginx) -
第四步:修改index.html 中的所有静态资源访问路径 href 与 src标签的 统一加上/static/search/
网关服务 添加新的路由规则 Host为search.mall.com的 统一转交给search服务处理,并重启网关服务
测试
-
确保search搜索服务 网关服务都成功注册进nacos服务注册中心
-
nginx容器配置修改并重启
-
浏览器访问
search.mall.com
,出现如下效果则搭建完成
检索业务分析
检索参数模型分析抽取
-
商品检索的三个入口:
-
1、首页--选择商品分类进入商品检索(分类catalogId)
-
2、输入检索关键字展示检索页 (搜索关键字keyword)
-
3、选择筛选条件进入
-
-
检索条件分析
-
1、全文检索:skuTitle -> keyword
-
2、排序:saleCount(销量)、hotScore(热度评分)、skuPrice(价格)
-
3、过滤:hasStock(是否有货)、skuPrice(价格区间)、brandId(品牌id 可以多选)、catalogId
-
4、聚合:attrs(属性)
完整请求参数: catalog3Id=225&keyword=小米&hasStock=0/1&skuPrice=1_500&brandId=1&attrs=1_android:iOS&attrs=2_5.寸:6.5寸 排序条件:sort=saleCount_asc/desc、sort=skuPrice_asc/desc、sort=hostScore_asc/desc hasStock(是否有货): hasStock=0/1 skuPrice价格区间: skuPrice=1_500/_500/500_(1到500、500以内的、大于500的) brandId(品牌id 可以多选):brandId=1&brandId=2 attrs(属性): attrs=1_android:iOS&attrs=2_5.寸:6.5寸
-
检索结果模型分析抽取
-
根据页面内容,抽取返回数据
SearchResult 查询到的所有商品信息 当前页码 总记录数 总页码 当前查询到的结果,所涉及到的所有品牌 当前查询到的结果,所涉及到的所有分类 当前查询的结果,所涉及到的所有属性
检索语句构建(DSL)
- 启动ES和kibana容器
查询部分语句
-
在kibana中先编写DSL语句,理清思路
GET product/_search { "query": { "bool": { "must": [ { "match": { "skuTitle": "小米" } } ], "filter": [ { "term": { "catalogId": "225" } }, { "terms": { "brandId": [ "3", "10" ] } }, { "nested": { "path": "attrs", "query": { "bool": { "must": [ { "term": { "attrs.attrId": { "value": "13" } } }, { "terms": { "attrs.attrValue": [ "android" ] } } ] } } } }, { "term": { "hasStock": { "value": "true" } } }, { "range": { "skuPrice": { "gte": 100, "lte": 4000 } } } ] } }, "sort": [ { "skuPrice": { "order": "desc" } } ], "from": 0, "size": 1, "highlight": { "fields": {"skuTitle": {}}, "pre_tags": "<b style='color:red'>", "post_tags": "</b>" } }
如果是嵌入式的属性 nested ,查询、聚合、分析都应该用嵌入式的
DSL聚合分析
ES数据迁移
-
由于之前的ES中的 product索引 映射关系设置有些问题,我们可以新建一个新的mall_product索引,修改映射关系,并且把原有数据迁移进mall_product
#数据迁移 PUT mall_product { "mappings": { "properties": { "skuId": { "type": "long" }, "spuId": { "type": "keyword" }, "skuTitle": { "type": "text", "analyzer": "ik_smart" }, "skuPrice": { "type": "keyword" }, "skuImg": { "type": "keyword" }, "saleCount": { "type": "long" }, "hasStock": { "type": "boolean" }, "hotScore": { "type": "long" }, "brandId": { "type": "long" }, "catalogId": { "type": "long" }, "brandName": { "type": "keyword" }, "brandImg": { "type": "keyword" }, "catalogName": { "type": "keyword" }, "attrs": { "type": "nested", "properties": { "attrId": { "type": "long" }, "attrName": { "type": "keyword" }, "attrValue": { "type": "keyword" } } } } } } #转移product数据到mall_product #固定写法 POST _reindex { "source": { "index": "product" }, "dest": { "index": "mall_product" } } #查看数据迁移情况 GET mall_product/_search { "query": { "match_all": {} } }
聚合分析
-
最终结果
#检索 查询&聚合 GET mall_product/_search { "query": { "bool": { "must": [ { "match": { "skuTitle": "小米" } } ], "filter": [ { "term": { "catalogId": "225" } }, { "terms": { "brandId": [ "3", "10" ] } }, { "nested": { "path": "attrs", "query": { "bool": { "must": [ { "term": { "attrs.attrId": { "value": "13" } } }, { "terms": { "attrs.attrValue": [ "android" ] } } ] } } } }, { "term": { "hasStock": { "value": "true" } } }, { "range": { "skuPrice": { "gte": 100, "lte": 4000 } } } ] } }, "sort": [ { "skuPrice": { "order": "desc" } } ], "from": 0, "size": 1, "highlight": { "fields": {"skuTitle": {}}, "pre_tags": "<b style='color:red'>", "post_tags": "</b>" }, "aggs": { "brand_agg": { "terms": { "field": "brandId", "size": 10 }, "aggs": { "brand_name_agg": { "terms": { "field": "brandName", "size": 10 } }, "brand_img_agg": { "terms": { "field": "brandImg", "size": 10 } } } }, "catalog_agg":{ "terms": { "field": "catalogId", "size": 10 }, "aggs": { "catalog_name_aggs": { "terms": { "field": "catalogName", "size": 10 } } } }, "attr_agg":{ "nested": { "path": "attrs" }, "aggs": { "attr_id_agg": { "terms": { "field": "attrs.attrId", "size": 10 }, "aggs": { "attr_name_agg": { "terms": { "field": "attrs.attrName", "size": 10 } }, "attr_value_agg": { "terms": { "field": "attrs.attrValue", "size": 10 } } } } } } } }
-
分析过程
当前查询到的结果 所有涉及到的所有品牌 当前查询到的结果 所有涉及到的所有属性 当前查询到的结果 所有涉及到的所有分类
-
所有品牌(分类同理)
GET mall_product/_search { "query": { "match_all": {} }, "aggs": { "brand_agg": { "terms": { "field": "brandId", "size": 10 } } } }
-
想要顺便获取品牌的名称和品牌logo
GET mall_product/_search { "query": { "match_all": {} }, "aggs": { "brand_agg": { "terms": { "field": "brandId", "size": 10 }, "aggs": { "brand_name_agg": { "terms": { "field": "brandName", "size": 10 } }, "brand_img_agg": { "terms": { "field": "brandImg", "size": 10 } } } } } }
-
当前查询结果下的所有属性(*)
-
属性是nested的嵌套,所以聚合分析也应该是聚合的
GET mall_product/_search { "query": { "match_all": {} }, "aggs": { "attr_agg":{ "nested": { "path": "attrs" }, "aggs": { "attr_id_agg": { "terms": { "field": "attrs.attrId", "size": 10 }, "aggs": { "attr_name_agg": { "terms": { "field": "attrs.attrName", "size": 10 } }, "attr_value_agg": { "terms": { "field": "attrs.attrValue", "size": 10 } } } } } } } }
-
-
代码实现
SearchRequest构建
-
java实现动态构建出查询需要DSL语句
-
检索
--bool query --must --term --filter --term --terms --term --range --nested --bool --must --term --term
-
分页、排序、高亮
-
聚合
--brand_agg --brand_name_agg --brand_img_agg --catalog_agg --catalog_name_agg --nestedAttr_agg --attr_id_agg --attr_name_agg --attr_value_agg
-
-
测试检索语句构建是否正确
-
第一种:
- postman 简单请求(keyword、catalog3Id)
-
打印 searchSourceBuilder,并使用在线json格式化工具 格式化
-
如果语句构建正确,可以直接拷贝到kibana中查询
-
第二种:
-
复杂筛选(属性attrs、价格区间)
-
-
-
测试聚合语句
-
postman模拟请求
-
打印出的语句,拷贝到kibana
-
SearchResponse分析&封装
-
将ES返回结果进行封装
1、返回的所有查询到的商品 2、当前所有商品所涉及到的所有属性 3、当前所有商品所涉及到的所有品牌信息 4、当前所有商品所涉及到的所有分类信息 5、分页信息
页面渲染
页面基本数据渲染
-
检索数据展示
-
刷选条件选择,请求路径拼接
#"brandId" 代表"brandId" 前后双引号 th:href="${'javascript:searchProducts("brandId",'+brand.brandId+')'}
-
关键字检索
检索条件回显
-
分页数据渲染
-
排序效果
- 排序
-
排序字段回显(页面跳转刷选之后)
-
面包屑导航
-
条件筛选联动