- 模糊匹配
- 过滤(按照属性、分类、品牌、价格区间、库存)
- 排序
- 分页
- 高亮
- 聚合分析
一. 搜索关键字![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/4622a99bd30caf385c528daf6f9de6c2.png)
检索字段:商品sku标题
“skuTitle” : “华为 HUAWEI Mate 30 Pro 亮黑色 8GB+256GB麒麟990旗舰芯片OLED环幕屏双4000万徕卡电影四摄4G全网通手机”
bool复合查询,must必须,全文检索字段用 match,其他非 text 字段匹配用 term
GET product/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"skuTitle": "华为"
}
}
]
}
}
}
二. 检索分类
检索字段:分类id
“catalogId” : 225
match会计算热度评分,filter不计算分数效率更快,所有把不需要热度评分的字段放大filter
全文检索字段用 match,其他非 text 字段匹配用 term
GET product/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"skuTitle": "华为"
}
}
],
"filter": {
"term": {
"catalogId": "225"
}
}
}
}
}
三. 检索品牌
品牌是可以多选的,检索条件为品牌id的集合
terms等价于mysql 的 in()
检索字段:品牌Id
“brandId” : 9
GET product/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"skuTitle": "华为"
}
}
],
"filter": [
{
"term": {
"catalogId": "225"
}
},
{
"terms": {
"brandId": [
"1",
"2",
"9"
]
}
}
]
}
}
}
四. 检索属性
: attrId----------attrValue
属性可多选
查询attrs属性下嵌入的属性attr_id需要使用nested 嵌套查询
"attrs" : [
{
"attrId" : 15,
"attrName" : "CPU品牌",
"attrValue" : "高通(Qualcomm)"
},
{
"attrId" : 16,
"attrName" : "CPU型号",
"attrValue" : "骁龙855"
}
]
检索字段:属性id、属性值
“attrId” : 15,
“attrValue” : “高通(Qualcomm)”
GET product/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"skuTitle": "华为"
}
}
],
"filter": [
{
"term": {
"catalogId": "225"
}
},
{
"terms": {
"brandId": [
"1",
"2",
"9"
]
}
},
{
"nested": {
"path": "attrs",
"query": {
"bool": {
"must": [
{
"term": {
"attrs.attrId": {
"value": "15"
}
}
},
{
"terms": {
"attrs.attrValue": [
"高通(Qualcomm)",
"以官网信息为准"
]
}
}
]
}
}
}
}
]
}
}
}
五. 检索库存、排序、价格区间、分页
查询是否有库存
排序
查询价格区间
分页
from从第几页开始,size查询几天记录
六.修改映射
{
"gulimall_product" : {
"mappings" : {
"properties" : {
"attrs" : {
"type" : "nested",
"properties" : {
"attrId" : {
"type" : "long"
},
"attrName" : {
"type" : "keyword"
},
"attrValue" : {
"type" : "keyword"
}
}
},
"brandId" : {
"type" : "long"
},
"brandImg" : {
"type" : "keyword"
},
"brandName" : {
"type" : "keyword"
},
"catalogId" : {
"type" : "long"
},
"catalogName" : {
"type" : "keyword"
},
"hasStock" : {
"type" : "boolean"
},
"hotScore" : {
"type" : "long"
},
"saleCount" : {
"type" : "long"
},
"skuId" : {
"type" : "long"
},
"skuImg" : {
"type" : "keyword"
},
"skuPrice" : {
"type" : "keyword"
},
"skuTitle" : {
"type" : "text",
"analyzer" : "ik_smart"
},
"spuId" : {
"type" : "keyword"
}
}
}
}
}
七.动态聚合
根据检索的条件,查到数据后,根据结果聚合,动态的展示搜索的属性内容
例:如何搜索了机身内存 128G,那么刷新页面后就不会展示机身内存这一栏属性
聚合terms
聚合中的terms 会按照字段的值来分类,结果使用doc_count展示
比如性别有男、女,就会创建两个桶,分别存放男女的信息。默认会搜集doc_count的信息,即记录有多少男生,有多少女生,然后返回给客户端,这样就完成了一个terms得统计。
//GET gulimall_product/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"skuTitle": "华为"
}
}
],
"filter": [
{
"term": {
"catalogId": "225"
}
},
{
"terms": {
"brandId": [
"1",
"2",
"9"
]
}
},
{
"nested": {
"path": "attrs",
"query": {
"bool": {
"must": [
{
"term": {
"attrs.attrId": {
"value": "15"
}
}
},
{
"terms": {
"attrs.attrValue": [
"高通(Qualcomm)",
"以官网信息为准"
]
}
}
]
}
}
}
},
{
"term": {
"hasStock": {
"value": "false"
}
}
},
{
"range": {
"skuPrice": {
"gte": 0,
"lte": 6000
}
}
}
]
}
},
"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_agg": {
"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
}
}
}
}
}
}
}
}
八.后端构建DSL
//构建dsl语句
private SearchRequest buildSearchRequest(SearchParam searchParam){
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//最外层bool
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
/**
* 1.must
*/
if (!StringUtils.isEmpty(searchParam.getKeyword())){
boolQueryBuilder.must(QueryBuilders.matchQuery("skuTitle",searchParam.getKeyword()));
}
/**
* 2.filter
*/
//分类
if (searchParam.getCatalog3Id() != null){
boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId",searchParam.getCatalog3Id()));
}
//品牌
if (null != searchParam.getBrand_id() && searchParam.getBrand_id().size() > 0){
boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId",searchParam.getBrand_id()));
}
//属性
if (searchParam.getAttrs() != null && searchParam.getAttrs().size() > 0){
//带多少值就有多少NestedQueryBuilder
//attrs=1_5寸:8寸 & attrs=2_16G:8G
for (String attrStr : searchParam.getAttrs()) {
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//"attrs.attrId"
String[] s = attrStr.split("_");//[1,5寸:8寸]
//"attrs.attrValue"
String[] split = s[1].split(":");//[5寸,8寸]
//query
boolQuery.must(QueryBuilders.termQuery("attrs.attrId", s[0]));
boolQuery.must(QueryBuilders.termsQuery("attrs.attrValue",split));
//nestedQuery(String path, QueryBuilder query, ScoreMode scoreMode)
NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery("attrs", boolQuery, ScoreMode.None);
boolQueryBuilder.filter(nestedQueryBuilder);
}
}
//是否库存
if (searchParam.getHasStock() != null){
//传0,1, es存的bool
boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock",searchParam.getHasStock()==1));
}
//价格区间
if (!StringUtils.isEmpty(searchParam.getSkuPrice())){
RangeQueryBuilder rangeQueryBuilder = new RangeQueryBuilder("skuPrice");
//1_500/_500/500_
String[] s = searchParam.getSkuPrice().split("_");
//_500
if (searchParam.getSkuPrice().startsWith("_")){
rangeQueryBuilder.lte(s[1]);
}
//500_
if (searchParam.getSkuPrice().endsWith("_")){
rangeQueryBuilder.gte(s[0]);
}
//1_500
if (s.length == 2 && StringUtils.isNotBlank(s[0])&&StringUtils.isNotBlank(s[1])){
rangeQueryBuilder.gte(s[0]).lte(s[1]);
}
boolQueryBuilder.filter(rangeQueryBuilder);
}
//封装全部查询条件
searchSourceBuilder.query(boolQueryBuilder);
/**
* 3.排序、分页、高亮
*/
//排序
if (!StringUtils.isEmpty(searchParam.getSort())){
//根据热点评分排序sort=hotScore_asc/desc
String[] s = searchParam.getSort().split("_");
SortOrder sortOrder = s[1].equalsIgnoreCase("asc") ? SortOrder.ASC: SortOrder.DESC;
searchSourceBuilder.sort(s[0],sortOrder);
}
//分页
//from = (pageNum - 1) * pageSize
searchSourceBuilder.from((searchParam.getPageNum() - 1) * EsConstant.GULIMALL_PRODUCT_PAGESIZE);
searchSourceBuilder.size(EsConstant.GULIMALL_PRODUCT_PAGESIZE);
//高亮
if (!StringUtils.isEmpty(searchParam.getKeyword())){
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("skuTitle");
highlightBuilder.preTags("<b style='color:red'>");
highlightBuilder.postTags("</b>");
searchSourceBuilder.highlighter(highlightBuilder);
}
/**
* 4.聚合
*/
//brand_agg
TermsAggregationBuilder aggregationBuilder1 = AggregationBuilders.terms("brand_agg").field("brandId").size(50);
aggregationBuilder1.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(1));
aggregationBuilder1.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(1));
searchSourceBuilder.aggregation(aggregationBuilder1);
//catalog_agg
TermsAggregationBuilder aggregationBuilder2 = AggregationBuilders.terms("catalog_agg").field("catalogId").size(20);
aggregationBuilder2.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));
searchSourceBuilder.aggregation(aggregationBuilder2);
//attr_agg
NestedAggregationBuilder nested = AggregationBuilders.nested("attr_agg", "attrs");
TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId").size(10);
nested.subAggregation(attr_id_agg);
attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));
attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));
searchSourceBuilder.aggregation(nested);
System.out.println("DSL测试成功:" + searchSourceBuilder.toString());
//返回数据
//SearchRequest(String[] indices, SearchSourceBuilder source) {
SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.GULIMALL_PRODUCT_INDEX},searchSourceBuilder);
return searchRequest;
}
九. DSL返回结果封装
/**
* 通过 首页检索条件 或 分类 查询页面
*/
@Override
public SearchResult search(SearchParam searchParam) throws IOException {
//构建DSL
SearchRequest searchRequest = buildSearchRequest(searchParam);
SearchResult searchResult = null;
try {
//发送检索请求
SearchResponse searchResponse = client.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
//分析响应数据,转换成需要的格式
searchResult = buildSearchResult(searchResponse,searchParam);
} catch (IOException e) {
e.printStackTrace();
}
return searchResult;
}
private SearchResult buildSearchResult(SearchResponse searchResponse,SearchParam param) {
SearchResult result = new SearchResult();
SearchHits hits = searchResponse.getHits();
List<SkuEsModel> modelList = new ArrayList<>();
SearchHit[] hitArray = hits.getHits();
for (SearchHit documentFields : hitArray) {
String string = documentFields.getSourceAsString();
SkuEsModel skuEsModel = JSON.parseObject(string, SkuEsModel.class);
modelList.add(skuEsModel);
//高亮
if (!StringUtils.isEmpty(param.getKeyword())){
//如果传了检索条件,我们的结果才使用高亮
HighlightField skuTitle = documentFields.getHighlightFields().get("skuTitle");
Text[] fragments = skuTitle.fragments();
String s = fragments[0].toString();
skuEsModel.setSkuTitle(s);
}
}
//查询所有商品信息
result.setProducts(modelList);
//分页信息
long total = hits.getTotalHits().value;
result.setPageNum(param.getPageNum());
result.setTotal(total);
//总页码计算:10/2 5 totalPage=5 , 11/2=5..1
int count = (int)total / (int)EsConstant.GULIMALL_PRODUCT_PAGESIZE;
result.setTotalPages(total % EsConstant.GULIMALL_PRODUCT_PAGESIZE == 0 ? count : ++count);
Aggregations aggregations = searchResponse.getAggregations();
//品牌信息
List<SearchResult.BrandVo> brands = new ArrayList<>();
ParsedLongTerms brand_agg = aggregations.get("brand_agg");
for (Terms.Bucket bucket : brand_agg.getBuckets()) {
SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
//brandId
long brandId = bucket.getKeyAsNumber().longValue();
brandVo.setBrandId(brandId);
//brandName
ParsedStringTerms brand_name_agg = bucket.getAggregations().get("brand_name_agg");
String brand_name = brand_name_agg.getBuckets().get(0).getKeyAsString();
brandVo.setBrandName(brand_name);
//brandImg
ParsedStringTerms brand_img_agg = bucket.getAggregations().get("brand_img_agg");
String brand_img = brand_img_agg.getBuckets().get(0).getKeyAsString();
brandVo.setBrandImg(brand_img);
brands.add(brandVo);
}
result.setBrands(brands);
//分类信息
List<SearchResult.CatalogVo> catalogVoList = new ArrayList<>();
ParsedLongTerms catalog_agg = aggregations.get("catalog_agg");
for (Terms.Bucket bucket : catalog_agg.getBuckets()) {
SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();
//catalogId
catalogVo.setCatalogId(Long.parseLong(bucket.getKeyAsString()));
//catalogName
ParsedStringTerms catalog_name_agg = bucket.getAggregations().get("catalog_name_agg");
String catalogName = catalog_name_agg.getBuckets().get(0).getKeyAsString();
catalogVo.setCatalogName(catalogName);
catalogVoList.add(catalogVo);
}
result.setCatalogs(catalogVoList);
//属性信息
List<SearchResult.AttrVo> attrs = new ArrayList<>();
ParsedNested attr_agg = aggregations.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();
Long attrId = bucket.getKeyAsNumber().longValue();
//attrId
attrVo.setAttrId(attrId);
//attrName
ParsedStringTerms attr_name_agg = bucket.getAggregations().get("attr_name_agg");
String attrName = attr_name_agg.getBuckets().get(0).getKeyAsString();
attrVo.setAttrName(attrName);
//attrValue
ParsedStringTerms attr_value_agg = bucket.getAggregations().get("attr_value_agg");
List<String> collect = attr_value_agg.getBuckets().stream().map(item -> {
return item.getKeyAsString();
}).collect(Collectors.toList());
attrVo.setAttrValue(collect);
attrs.add(attrVo);
}
result.setAttrs(attrs);
//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();
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 = (AttrResponseVo)r.getData("attr", new TypeReference<AttrResponseVo>() {});
navVo.setNavName(data.getAttrName());
} else {
navVo.setNavName(s[0]);
}
//2、取消了这个面包屑以后,我们要跳转到哪个地方,将请求的地址url里面的当前置空
//拿到所有的查询条件,去掉当前
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.getBrand_id() != null && param.getBrand_id().size() > 0){
List<SearchResult.NavVo> navs = result.getNavs();
SearchResult.NavVo navVo = new SearchResult.NavVo();
navVo.setNavName("品牌");
//TODO 远程查所有品牌
R r = productFeignService.brandsInfo(param.getBrand_id());
if (r.getCode() == 0){
List<BrandVo> brand = (List<BrandVo>)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);
}
return result;
}