精华内容
下载资源
问答
  • 商品ElasticSearch查询改造

    千次阅读 2016-08-05 15:44:57
    使用elasticsearch的一点心得

    商品ElasticSearch的查询改造

    需求

    因数据库慢查询越来越多,很多慢查询已经到了无法优化的地步(覆盖的量很大)并且索引已经设计到极限,例如按照时间进行索引的,时间平均分布的,所以索引命中很多数据。无法有效过滤大量的条数。所以进行改造,商品的模糊查询是 like %%的操作,所以导致数据库查询时间较长。

    操作

    现需要对商品name进行模糊查询的操作转移到Elasticsearch中,采用elasticsearch中的 wildware的操作来进行,其实这个操作也是扫描很多字段,但是可以减少数据库慢查询,但是这个通配符匹配也是所有数据进行查询,理论上来说没有达到优化的效果。可认为是解决数据库慢查询的一个方式。

    展望

    未来模糊查询应该会被代替,使用分词进行查询,将字段设置为可以分词的字段,使用对应的关键字进行检索,就可以提高查询速度。

    需要解决的问题

    1.数据量很大的时候需要按照一定的内容建立索引
    2.路由策略的选取,因为现在我们的路由策略是es默认的路由策略,可以平均分布,但是可能我们要查询的内容分布在不同的分片上面,这个时候,我们就要费力气,比如一个卖家的商品只需要查询一个分片就可以查询出来,但是现在我们却要查询多个分片,导致时间和空间的浪费。未来修改路由策略也可以减少很多时间空间的浪费。
    3.分词的选取,模糊搜索的好处是 与词义无关,这样不管用户输入什么词组都可以查询出对应的内容,但是使用分词器后,会对字段进行分词,这个时候 输入与词义无关的情况的时候就会出现查询不出来的问题。例如NIKE鞋 这个本身是一个词,但是用户搜索IKE谢的时候却无法搜索出来。要解决这种事情,目前没有好的办法,要速度还是精确匹配,要有一定的取舍(例如在JD的搜索框内搜索,联想笔记本就可以搜索出很多相关的内容,但是搜索记本的时候 相关度就有所下降,但是jd 提供了一个子查询的内容,可以通过子查询查询出对应的内容!)

    操作查询ES:

    Flush

    这个程序用来进行初始化数据来使用。我们建立索引后,要将数据库的数据导入到ES中,这个时候可以使用这个程序进行跑数据,将数据库的数据跑到es里面。

    collector

    这个程序用来监听商品的变更日志,来更新es中的数据,更新状态等操作。

    searcher

    这个服务主要用来查询ES的数据。一个es的查询客户端

    目前创建了Elasticsearch的索引结构

    POST http://127.0.0.1:9200/product_info_extends/
    {
       "mappings": {
          "product_info_extends": {
             "properties": {
                "sendProvinceId": {
                   "type": "long"
                },
                "imgKey": {
                   "type": "string",
                   "index": "no"
                },
                "noFree": {
                   "type": "string",
                   "index": "not_analyzed"
                },
                "score": {
                   "type": "long"
                },
                "type": {
                   "type": "long"
                },
                "promotionId": {
                   "type": "string",
                   "index": "not_analyzed"
                },
                "auditType": {
                   "type": "long"
                },
                "taourl": {
                   "type": "string",
                   "index": "no"
                },
                "level": {
                   "type": "string",
                   "index": "not_analyzed"
                },
                "firstPostPrice": {
                   "type": "long"
                },
                "offlinePermission": {
                   "type": "string",
                   "index": "no"
                },
                "productSku": {
                   "type": "string",
                   "index": "no"
                },
                "promotionEdt": {
                   "null_value": 0,
                   "format": "basic_date_time",
                   "type": "date",
                   "doc_values": true
                },
                "shelf": {
                   "type": "string",
                   "index": "no"
                },
                "offlineTime": {
                   "null_value": 0,
                   "format": "basic_date_time",
                   "type": "date",
                   "doc_values": true
                },
                "stockSku": {
                   "type": "string",
                   "index": "no"
                },
                "createUserId": {
                   "type": "long"
                },
                "sellerSendtime": {
                   "type": "long"
                },
                "mntProductNum": {
                   "type": "string",
                   "index": "not_analyzed"
                },
                "lastPromotionSdt": {
                   "null_value": 0,
                   "format": "basic_date_time",
                   "type": "date",
                   "doc_values": true
                },
                "lastPromotionEdt": {
                   "null_value": 0,
                   "format": "basic_date_time",
                   "type": "date",
                   "doc_values": true
                },
                "productSnapshootId": {
                   "type": "string",
                   "index": "not_analyzed"
                },
                "offlineType": {
                   "type": "long"
                },
                "addPostPrice": {
                   "type": "long"
                },
                "pDesc": {
                   "type": "string",
                   "index": "no"
                },
                "detailState": {
                   "type": "long"
                },
                "lockType": {
                   "type": "long"
                },
                "aptitudeImgKey": {
                   "type": "string",
                   "index": "no"
                },
                "subjectId": {
                   "type": "long"
                },
                "deliverDesc": {
                   "type": "string",
                   "index": "no"
                },
                "comment": {
                   "type": "string",
                   "index": "no"
                },
                "createTime": {
                   "null_value": 0,
                   "format": "basic_date_time",
                   "type": "date",
                   "doc_values": true
                },
                "taoCid": {
                   "type": "string",
                   "index": "no"
                },
                "lastUpdateTime": {
                   "null_value": 0,
                   "format": "basic_date_time",
                   "type": "date",
                   "doc_values": true
                },
                "state": {
                   "type": "long"
                },
                "sendType": {
                   "type": "long"
                },
                "customized": {
                   "type": "long"
                },
                "promotionCnt": {
                   "type": "long"
                },
                "maxBuyLimit": {
                   "type": "long"
                },
                "id": {
                   "type": "string",
                   "index": "not_analyzed"
                },
                "title": {
                   "type": "string",
                   "index": "not_analyzed"
                },
                "name": {
                   "type": "string",
                   "index": "not_analyzed"
                },
                "deleteYn": {
                   "type": "long"
                },
                "postType": {
                   "type": "long"
                },
                "sendAddress": {
                   "type": "string",
                   "index": "no"
                },
                "updatePermission": {
                   "type": "string",
                   "index": "no"
                },
                "groupId": {
                   "type": "long"
                },
                "lastUpdateUserId": {
                   "type": "long"
                },
                "advertisementCheck": {
                   "type": "long"
                },
                "offlineReason": {
                   "type": "string",
                   "index": "no"
                },
                "promotionSdt": {
                   "null_value": 0,
                   "format": "basic_date_time",
                   "type": "date",
                   "doc_values": true
                },
                "sellerId": {
                   "type": "long"
                },
                "productUrl": {
                   "type": "string",
                   "index": "no"
                },
                "attributes": {
                   "type": "string",
                   "index": "no"
                },
                "shortName": {
                   "type": "string",
                   "index": "no"
                },
                "stockInfo": {
                   "type": "nested",
                   "properties": {
                      "propertyNum": {
                         "type": "string",
                         "index": "not_analyzed"
                      },
                      "propertyName": {
                         "type": "string",
                         "index": "not_analyzed"
                      },
                      "activeProductCount": {
                         "type": "long"
                      },
                      "sellerClassNum": {
                         "type": "string",
                         "index": "no"
                      },
                      "shelf": {
                         "type": "string",
                         "index": "no"
                      },
                      "curPrice": {
                         "type": "long"
                      },
                      "orgPrice": {
                         "type": "long"
                      },
                      "lockProductCount": {
                         "type": "long"
                      },
                      "comment": {
                         "type": "string",
                         "index": "no"
                      },
                      "lastUpdateTime": {
                         "null_value": 0,
                         "format": "basic_date_time",
                         "type": "date",
                         "doc_values": true
                      },
                      "lastUpdateUserId": {
                         "type": "long"
                      },
                      "createTime": {
                         "null_value": 0,
                         "format": "basic_date_time",
                         "type": "date",
                         "doc_values": true
                      },
                      "createUserId": {
                         "type": "long"
                      },
                      "vPicture": {
                         "type": "string",
                         "index": "no"
                      }
                   }
                },
                "propInfo": {
                   "type": "nested",
                   "properties": {
                      "id": {
                         "type": "long"
                      },
                      "propertiesName": {
                         "type": "string",
                         "index": "not_analyzed"
                      },
                      "propertiesValue": {
                         "type": "string",
                         "index": "not_analyzed"
                      },
                      "no": {
                         "type": "long"
                      },
                      "createTime": {
                         "null_value": 0,
                         "format": "basic_date_time",
                         "type": "date",
                         "doc_values": true
                      }
                   }
                },
                "audit_id": {
                   "type": "long"
                },
                "audit_submitTime": {
                   "null_value": 0,
                   "format": "basic_date_time",
                   "type": "date",
                   "doc_values": true
                },
                "audit_auditorId": {
                   "type": "long"
                },
                "audit_auditorName": {
                   "type": "string",
                   "index": "not_analyzed"
                },
                "audit_updateAuditorTime": {
                   "null_value": 0,
                   "format": "basic_date_time",
                   "type": "date",
                   "doc_values": true
                },
                "audit_auditResult": {
                   "type": "long"
                },
                "audit_auditFailDesc": {
                   "type": "string",
                   "index": "not_analyzed"
                },
                "audit_commission": {
                   "type": "string",
                   "index": "not_analyzed"
                },
                "audit_auditTime": {
                   "null_value": 0,
                   "format": "basic_date_time",
                   "type": "date",
                   "doc_values": true
                },
                "audit_auditFlag": {
                   "type": "long"
                },
                "expand_id": {
                   "type": "long"
                },
                "expand_sizeModelId": {
                   "type": "string",
                   "index": "not_analyzed"
                },
                "expand_conditionalReturn": {
                   "type": "long"
                },
                "expand_pintuanFlag": {
                   "type": "long"
                },
                "freightTemplateId": {
                   "type": "long"
                }
             }
          }
       }
    }
    

    以上创建索引的内容中type有几种形式

    long

    存储长整形类型
    就是存储一个数字长度(long 型存储 0,1这种类型的时候会有浪费空间的情况发生可以采用其余的基本类型)

    date

    存储日期类型

    "null_value": 0,
    "format": "basic_date_time",
    "type": "date",
    "doc_values": true
    

    format的类型就是可以通过哪种方式往es里面存放时间类型。这个地方我试验了 2016-08-01 12:00:00 这种方式,可以进行插入和转化,但是精心range 查询的时候就会出现某些问题,例如查询不在指定范围内的情况发生。
    所以现在是以 millins 的方式进行插入的,也可以插入到这个类型的字段中,并且可以通过millins的查询。
    不过建议以后还是使用long型存储时间,可以节省很多不必要的麻烦。就是转换的时候会需要解析一下而已。

    Nest

    数组类型
    es中支持对象数组,可以将数据结构中的list,或数据库中的一对多存储到这个里面,但是要注意,查询的时候要指定查询的path。

    String

    字符串通常字符串会配合 index进行建立
    index: not_analyzed , no ,analyzed 三种

    "index": "no",   如果这么设置,那么这个字段是没有办法被查询出来的,只能被一起带出来,不参与查询
    "type": "string"
    
    "index": "not_analyzed", 不分析 但是可以查,传入什么就按照什么查,例如,如果存储了一个 ze111111,那么这个词存储倒排索引的时候就是按照 ze111111进行存储的,这样查询的时候也只能通过 ze111111,才能查出来 通过ze 是没有办法查询出来的,
    "type": "string"
    
    "index":""  // 指定对应的解析器  设置对应的解析器,然后通过解析器后的分词进行查询,ze111111 分词器如果把他分为 ze 和 111111,那么 通过ze就可以查询到了。
    "type":"string" 
    

    索引的修改

    需要指定对应的索引,对应的type名称进行修改。

    PUT http://192.168.10.163:9200/product_info_extends/_mapping/product_info_extends
    {
       "properties": {
          "freightTemplateId": {
             "type": "long"
          },
          "brandId": {
             "type": "string",
             "index": "not_analyzed"
          }
       }
    }
    

    查询相关

    Cache

    使用cache会自动cache到内存中的pagesize中,提高查询速度

    POST http://192.168.10.163:9200/product_info_extends/_search
    {
       "from": 0,
       "size": 20,
       "sort": [
          {
             "lastUpdateTime": {
                "order": "desc"
             }
          }
       ],
       "query": {
          "filtered": {
             "filter": {
                "bool": {
                   "must": [
                      {
                         "terms": {
                            "state": [
                               "2",
                               "3"
                            ]
                         }
                      },
                      {
                         "term": {
                            "sellerId": 661954
                         }
                      },
                      {
                         "terms": {
                            "subjectId": [
                               4897
                            ]
                         }
                      }
                   ],
                   "must_not": [
                      {
                         "query": {
                            "term": {
                               "deleteYn": 1
                            }
                         }
                      }
                   ],
                   "_cache": true
                }
             }
          }
       }
    }
    

    数组查询

    POST http://192.168.10.225:9203/product_info_extends/_search
    {
       "from": 0,
       "size": 20,
       "sort": [
          {
             "createTime": {
                "order": "desc"
             }
          }
       ],
       "query": {
          "filtered": {
             "filter": {
                "bool": {
                   "must": [
                      {
                         "term": {
                            "sellerId": "661954"
                         }
                      },
                      {
                         "query": {
                            "nested": {
                               "path": "propInfo",
                               "query": {
                                  "bool": {
                                     "must": [
                                        {
                                           "term": {
                                              "propertiesValue": "麻"
                                           }
                                        },
                                        {
                                           "term": {
                                              "propertiesName": "材质"
                                           }
                                        }
                                     ]
                                  }
                               }
                            }
                         }
                      }
                   ],
                   "_cache": true
                }
             }
          }
       }
    }
    

    Nest + Wildcard 查询

    POST http://192.168.10.163:9200/product_info_extends/_search
    {
       "from": 0,
       "size": 20,
       "query": {
          "filtered": {
             "filter": {
                "bool": {
                   "must": [
                      {
                         "query": {
                            "nested": {
                               "path": "stockInfo",
                               "query": {
                                  "bool": {
                                     "must": [
                                        {
                                           "wildcard": {
                                              "propertyName": "*:颜色*"
                                           }
                                        }
                                     ]
                                  }
                               }
                            }
                         }
                      }
                   ]
                }
             }
          }
       }
    }
    

    时间查 0(一般0设置为默认值)

    时间按照 0 去查询,一般时间会按照 date 类型进行存储,但是有时候没有时间的时候我们会给时间设置一个默认值方面去查询,这个时候就要用 0 去查询了,但是用0 的时候 并且用term 去查询的时候会出现问题。
    0 is too short ,这个时候一般可以使用

    {
       "query": {
          "match": {
             "promotionSdt": {
                "query": 0,
                "type": "phrase"
             }
          }
       }
    }
    进行代替
    

    关于phrase

    http://www.cnblogs.com/yjf512/p/4897294.html 这边文章写了一下 phrase 的用法 可以参考一下

    field查询 只取回对应的字段的内容

    POST http://192.168.10.163:9200/product_info_extends/_search
    {
       "from": 0,
       "size": 20,
       "fields": [
          "id",
          "stockInfo.propertyName"
       ],
       "query": {
          "filtered": {
             "filter": {
                "bool": {
                   "must": [
                      {
                         "query": {
                            "nested": {
                               "path": "stockInfo",
                               "query": {
                                  "bool": {
                                     "must": [
                                        {
                                           "wildcard": {
                                              "propertyName": "*:颜色*"
                                           }
                                        }
                                     ]
                                  }
                               }
                            }
                         }
                      }
                   ]
                }
             }
          }
       }
    }
    

    通过curl 进行查询并且导入到对应的文件中

    curl -XGET ‘192.168.10.163:9200/product_info_extends/_search?pretty’ -d @searchPropertyName > 11.txt
    加入pretty 参数可以将返回值设置的好看 (json format)

    @searchPropertyName
    {
       "from": 0,
       "size": 20,
       "fields": [
          "id","stockInfo.propertyName"
       ],
       "query": {
          "filtered": {
             "filter": {
                "bool": {
                   "must": [
                      {
                         "query": {
                            "nested": {
                               "path": "stockInfo",
                               "query": {
                                  "bool": {
                                     "must": [
                                        {
                                            "wildcard": {
                                                "propertyName": "*:颜色*"
                                            }
                                        }
                                     ]
                                  }
                               }
                            }
                         }
                      }
                   ]
                }
             }
          }
       }
    }
    

    设置routing

    这样每次都放入到同一个分片上面来,这样的话查询的时候会直接去对应的分片上面进行查找。
    可能导致每个分片的大小不一致了。
    查的时候也需要指定routing参数。 这样直接去对应的shard 分片上进行查询。

    "test":{
        "_routing":{
              "required":true
                "path":"user"        按照user的值进行routing
          }
    }
    
    展开全文
  • Elasticsearch基于JSON提供完整的查询DSL(Domain Specific Language:领域特定语言)来定义查询。基本语法: GET /索引名/类型名/_search 一般都是需要配合查询参数来使用的,配合不同的参数有不同的查询效果. ...

    一、高级查询

    1、简介

    Elasticsearch基于JSON提供完整的查询DSL(Domain Specific Language:领域特定语言)来定义查询。
    基本语法: GET /索引名/类型名/_search
    一般都是需要配合查询参数来使用的,配合不同的参数有不同的查询效果.

    参数配置项可以参考博客:https://www.jianshu.com/p/6333940621ec

    2、结果排序

    参数格式:

    GET /索引/类型/_search
    { 
         "sort": [ 
    	{field: 排序规则},
    	... 
            ] 
    }
    

    排序规则:

    1. asc:表示升序
    2. desc:表示降序

    没有配置排序的情况下,默认按照评分降序排列
    示例如下:

    GET /user/user/_search
    {
      "sort": [
        {
          "age": {
            "order": "desc"
          }
        }
      ] 
    }
    

     

    3、分页查询

    参数格式:
    from:从什么地方开始
    size:到什么地方结束

    GET /索引/类型/_search
    { 
          "from": start,
          "size": pageSize 
    }
    

    示例如下:

    GET /product/product/_search
    {
      "from": 0
      , "size": 3
    }
    

    4、检索查询

    参数格式:

    GET /索引/类型/_search
    {
         "query": {
                 检索方式: {field: value}
     }
    

    检索方式:

    1)term表示精确匹配,value值不会被分词器拆分,按照倒排索引匹配 。
    2)match表示全文检索,value值会被分词器拆分,然后去倒排索引中匹配 。
    3)range表示范围检索,其value值是一个对象,如{ “range”: {field: {比较规则: value, …}} } 比较规则有gt / gte / lt / lte 等。

    注意:term和match都能用在数值和字符上,range多用在数值上。
    示例如下:

    # 查询商品中价格为 1899.99的
    GET /product/_search
    {
      "query": {
        "term": {
          "price": {
            "value": "1899.99"
          }
        }
      }
    }
    
    # 查询商品标题中带有鼠标,手机的
    GET /product/_search
    {
      "query": {
        "match": {
          "title": "小米 手机"
        }
      }
    }
    
    # 查询商品价格在1600~2800 之间的
    GET /product/_search
    {
      "query": {
        "range": {
          "price": {
            "gte": 1600,
            "lte": 2800
          }
        }
      }
    }
    

    5、关键字查询

    参数格式:

    GET /索引/类型/_search
    {
       "query": {
            "multi_match": { 
                 "query": value, 
                 "fields": [field1, field2, ...] 
                 } 
           }  
    }
    

    multi_match:表示在多个字段间做检索,只要其中一个字段满足条件就能查询出来,多用在字符上。
    示例如下:

    # 查询商品产地和标题中匹配 vivo 湛江 的数据
    GET /product/product/_search
    {
      "query": {
        "multi_match": {
          "query": "vivo 湛江",
          "fields": ["origin","title"]
        }
      }
    }
    

    6、高亮显示

    参数格式:

    GET /索引/类型/_search
    { 
       "query": { ... }, 
       "highlight": {
              "fields": { 
                    field1: {}, 
                    field2: {}, 
                    ... 
                 },
               "pre_tags": 开始标签, 
               "post_tags" 结束标签
          } 
    }
    

    highlight:表示高亮显示,需要在fields中配置哪些字段中检索到该内容需要高亮显示 必须配合检索(term / match)一起使用。
    示例如下:

    # 查询商品标题中带有鼠标的,并且高亮显示
    GET /product/_search
    {
      "query": {
        "match": {
          "title": "鼠标"
        }
      },
      "highlight": {
        "fields": {
          "title": {}
        }
      }
    }
    

    7、逻辑查询

    参数格式:

    GET /索引/类型/_search
    {
          "query": {
                "bool": {
                      逻辑规则: [ 
                       {检索方式: {field: value}},
                            ... 
                       ],
                      ... 
                  } 
           } 
    }
    

    逻辑规则:must / should / must_not,相当于and / or / not
    示例如下:

    # 查询手机的价格在1900~2800之间的
    GET /product/_search
    {
      "query": {
        "bool": {
          "must": [
            {
              "term": {
                "title": {
                  "value": "手机"
                }
              }
            },
            {
              "range": {
                "price": {
                  "gte": 1900,
                  "lte": 2800
                }
              }
            }
          ]
        }
      }
    }
    

    8、过滤查询

    参数格式:

    GET /索引/类型/_search
    { 
        "query": {
            "bool": {
               "filter": [ 
                    { 
                         检索方式: {
                               field: value 
                            } 
                    }, 
                    ... 
                 ] 
              } 
          } 
    }
    

    从效果上讲过滤查询和检索查询能做一样的效果。区别在于过滤查询不评分,结果能缓存,检索查询要评分,结果不缓存。 一般是不会直接使用过滤查询,都是在检索了一定数据的基础上再使用。
    示例如下:

    # 查询手机的价格在1900~2800之间的
    GET /product/_search
    {
      "query": {
        "bool": {
          "must": [
            {
              "term": {
                "title": {
                  "value": "手机"
                }
              }
            }
          ],
           "filter": {
              "range": {
                "price": {
                  "gte": 1900,
                  "lte": 2800
                }
              }
          }
        }
      }
    }
    

    9、分组查询

    参数格式:

    GET /索引/类型/_search 
    { 
    	"size": 0, 
    	"aggs": { 
    		自定义分组字段: {
    			"terms": { 
    				"field": 分组字段, 
    				"order": {自定义统计字段:排序规则}, 
    				"size": 10 //默认显示10组 
    				},
    				"aggs": { //分组后的统计查询,相当于MySQL分组函数查询 
    					自定义统计字段: { 分组运算: { "field": 统计字段 } 
    					} 
    				} 
    		} 
    	} 
    }
    
    

    分组运算:avg / sum / min / max / value_count / stats(执行以上所有功能的)
    注意:这里是size=0其目的是为了不要显示hit内容,专注点放在观察分组上。

    展开全文
  • Elasticsearch是一个基于Lucene的搜索服务器。提供了一个分布式多用户能力的全文搜索引擎,在前面的文章中讲解了单机、集群搭建、DSL语句、分词器、以及整合SpringBoot的Demo。 Elasticsearch专题篇 ElasticSearch...

    1.简介

    Elasticsearch是一个基于Lucene的搜索服务器。提供了一个分布式多用户能力的全文搜索引擎,在前面的文章中讲解了单机、集群搭建、DSL语句、分词器、以及整合SpringBoot的Demo。

    相关Elasticsearch专题篇

    1. ElasticSearch基本概念(索引,分片,节点,倒排索引…)
    2. ElasticSearch7.x单机版安装
    3. ElasticSearch7.x高可用集群版搭建
    4. Elasticsearch 7x 配置文件详解
    5. Elasticsearch客户端工具之kibana
    6. Elasticsearch客户端工具之ES-Head
    7. ElasticSearch7.x安全性之访问密码设置
    展开全文
  • Elasticsearch实现商品搜索商品搜索1、 根据关键字查询2、条件筛选 商品搜索 核心依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-...

    商品搜索

    核心依赖

        <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
            </dependency>
    

    1、根据关键字查询

    (1) changgou_service_search项目创建SearchService接口

    public interface SearchService {
    
        /**
         * 全文检索
         * @param paramMap  查询参数
         * @return
         */
        public Map search(Map<String, String> paramMap) throws Exception;
    }
    

    (2) changgou_service_search项目创建SearchService接口实现类SearchServiceImpl

    @Service
    public class SearchServiceImpl implements SearchService {
    
        @Autowired
        private ElasticsearchTemplate esTemplate;
    
    
    
        //设置每页查询条数据
        public final static Integer PAGE_SIZE = 20;
    
        @Override
        public Map search(Map<String, String> searchMap) throws Exception {
            Map<String, Object> resultMap = new HashMap<>();
    
            //有条件才查询Es
            if (null != searchMap) {
                //组合条件对象
                BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
                //0:关键词   模糊查询,相当于%关键词%  后面的and可用于多个拼接
                if (!StringUtils.isEmpty(searchMap.get("keywords"))) {
                    boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
                   
                }
    
                //4. 原生搜索实现类   把查询条件丢进去
                NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
                nativeSearchQueryBuilder.withQuery(boolQuery);
                
                //10: 执行查询, 返回结果对象
                AggregatedPage<SkuInfo> aggregatedPage = esTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class, new SearchResultMapper() {
                    @Override
                    public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
    
                        List<T> list = new ArrayList<>();
                         //拿到查询到的所有数据
                        SearchHits hits = searchResponse.getHits();
                        if (null != hits) {
                            for (SearchHit hit : hits) {
                            //格式转换后放入集合中
                                SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
    
                                list.add((T) skuInfo);
                            }
                        }
                        return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
                    }
                });
    
                //11. 总条数
                resultMap.put("total", aggregatedPage.getTotalElements());
                //12. 总页数
                resultMap.put("totalPages", aggregatedPage.getTotalPages());
                //13. 查询结果集合
                resultMap.put("rows", aggregatedPage.getContent());
                //查询的结果返回给前端
                return resultMap;
            }
            return null;
        }
    }
    

    (3) changgou_service_search项目创建SearchController

    @RestController
    @RequestMapping("/sku_search")
    public class SearchController {
    
        @Autowired
        private EsManagerService esManagerService;
    
        @Autowired
        private SearchService searchService;
        
         //对搜索入参带有特殊符号进行处理
        public void handlerSearchMap(Map<String,String> searchMap){
    
            if(null != searchMap){
                Set<Map.Entry<String, String>> entries = searchMap.entrySet();
                for (Map.Entry<String, String> entry : entries) {
                    if(entry.getKey().startsWith("spec_")){
                        searchMap.put(entry.getKey(),entry.getValue().replace("+","%2B"));
                    }
                }
            }
    
        }
    
        /**
         * 全文检索
         * @return
         */
        @GetMapping
        public Map search(@RequestParam Map<String, String> paramMap) throws Exception {
            //特殊符号处理
            handlerSearchMap(searchMap);
            Map resultMap = searchService.search(paramMap);
            return resultMap;
        }
    }
    

    (4)测试的时候可以多拼接keywords=xxx来实现关键字的搜索

    2、条件筛选

    在这里插入图片描述

    用户有可能会根据分类搜索品牌搜索,还有可能根据规格搜索,以及价格搜索和排序操作。根据分类和品牌搜索的时候,可以直接根据指定域搜索,而规格搜索的域数据是不确定的,价格是一个区间搜索,所以我们可以分为三段实现,先实现分类、品牌搜素,再实现规格搜索,然后实现价格区间搜索。

    2.1 品牌筛选

    2.1.1 需求分析

    页面每次向后台传入对应的分类和品牌,后台据分类和品牌进行条件过滤即可。

    2.1.2 代码实现

    修改搜索微服务com.changgou.service.SearchServiceImpl的搜索方法,添加品牌过滤,代码如下:

    代码如下:

    @Override
    public Map search(Map<String, String> searchMap) throws Exception {
        Map<String, Object> resultMap = new HashMap<>();
    
        //有条件才查询Es
        if (null != searchMap) {
            //组合条件对象
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            //0:关键词
            if (StringUtils.isNotEmpty(searchMap.get("keywords"))) {
                boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
            }
            //1:条件 品牌  精准匹配条件 比如 苹果
            if (StringUtils.isNotEmpty(searchMap.get("brand"))) {
                boolQuery.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
            }
            
            //4. 原生搜索实现类
            NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
            nativeSearchQueryBuilder.withQuery(boolQuery);
    
            //6. 品牌聚合(分组)查询   对于这个字段进行聚合查询,类似于group by
            String skuBrand = "skuBrand";
    nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));
    
            //10: 执行查询, 返回结果对象
            AggregatedPage<SkuInfo> aggregatedPage = esTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class, new SearchResultMapper() {
                @Override
                public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
    
                    List<T> list = new ArrayList<>();
    
                    SearchHits hits = searchResponse.getHits();
                    if (null != hits) {
                        for (SearchHit hit : hits) {
                            SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
    
                            list.add((T) skuInfo);
                        }
                    }
                    return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
                }
            });
    
            //11. 总条数
            resultMap.put("total", aggregatedPage.getTotalElements());
            //12. 总页数
            resultMap.put("totalPages", aggregatedPage.getTotalPages());
            //13. 查询结果集合
            resultMap.put("rows", aggregatedPage.getContent());
    
            //14. 获取品牌聚合结果
            StringTerms brandTerms = (StringTerms) aggregatedPage.getAggregation(skuBrand);
            List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
            resultMap.put("brandList", brandList);
    
            return resultMap;
        }
        return null;
    }
    

    测试时可多加入brand来实现品牌的指定查询

    2.2 规格过滤

    2.2.1 需求分析

    规格这一部分,需要向后台发送规格名字以及规格值,我们可以按照一定要求来发送数据,例如规格名字以特殊前缀提交到后台:spec_网络制式:电信4G、spec_显示屏尺寸:4.0-4.9英寸

    后台接到数据后,可以根据前缀spec_来区分是否是规格,如果以spec_xxx开始的数据则为规格数据,需要根据指定规格找信息。

    在这里插入图片描述
    spechMap.规格名字.keyword

    2.2.2 代码实现

    修改com.changgou.service.SearchServiceImpl的搜索方法,增加规格查询操作,代码如下:

    代码如下:

    @Override
    public Map search(Map<String, String> searchMap) throws Exception {
        Map<String, Object> resultMap = new HashMap<>();
    
        //有条件才查询Es
        if (null != searchMap) {
            //组合条件对象
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            //0:关键词
            if (!StringUtils.isEmpty(searchMap.get("keywords"))) {
                boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
            }
            //1:条件 品牌
            if (!StringUtils.isEmpty(searchMap.get("brand"))) {
                boolQuery.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
            }
    
            //2:条件 规格  和前端商量好指定传参的名称,spec_颜色=黑色  这样传递并做一个精准匹配
            for (String key : searchMap.keySet()) {
                if (key.startsWith("spec_")) {
                    String value = searchMap.get(key).replace("%2B", "+");
                    boolQuery.filter(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword",value));
                }
            }
    
            //4. 原生搜索实现类
            NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
            nativeSearchQueryBuilder.withQuery(boolQuery);
    
    
            //6. 品牌聚合(分组)查询
            String skuBrand = "skuBrand";
     nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));
    
            //7. 规格聚合(分组)查询
            String skuSpec = "skuSpec";
     nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuSpec).field("spec.keyword"));
    
            //10: 执行查询, 返回结果对象
            AggregatedPage<SkuInfo> aggregatedPage = esTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class, new SearchResultMapper() {
                @Override
                public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
    
                    List<T> list = new ArrayList<>();
    
                    SearchHits hits = searchResponse.getHits();
                    if (null != hits) {
                        for (SearchHit hit : hits) {
                            SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
    
                            list.add((T) skuInfo);
                        }
                    }
                    return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
                }
            });
    
            //11. 总条数
            resultMap.put("total", aggregatedPage.getTotalElements());
            //12. 总页数
            resultMap.put("totalPages", aggregatedPage.getTotalPages());
            //13. 查询结果集合
            resultMap.put("rows", aggregatedPage.getContent());
    
            //14. 获取品牌聚合结果
            StringTerms brandTerms = (StringTerms) aggregatedPage.getAggregation(skuBrand);
            List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
            resultMap.put("brandList", brandList);
    
            //15. 获取规格聚合结果
            StringTerms specTerms = (StringTerms) aggregatedPage.getAggregation(skuSpec);
            List<String> specList = specTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
            resultMap.put("specList", specList(specList));
    
            return resultMap;
        }
        return null;
    }
    
    //处理规格集合
    public Map<String, Set<String>> specList(List<String> specList) {
    
        Map<String, Set<String>> specMap = new HashMap<>();
    
        if (null != specList && specList.size() > 0) {
    
            for (String spec : specList) {
    
                Map<String, String> map = JSON.parseObject(spec, Map.class);
                Set<Map.Entry<String, String>> entries = map.entrySet();
                for (Map.Entry<String, String> entry : entries) {
                    String key = entry.getKey();
                    String value = entry.getValue();
    
                    Set<String> specValues = specMap.get(key);
                    if (null == specValues) {
                        specValues = new HashSet<>();
                    }
                    specValues.add(value);
                    specMap.put(key, specValues);
                }
            }
        }
        return specMap;
    }
    

    可加入spec_颜色=红色

    2.3 价格区间查询

    2.3.1 需求分析

    价格区间查询,每次需要将价格传入到后台,前端传入后台的价格大概是price=0-500或者price=500-1000依次类推,最后一个是price=3000,后台可以根据-分割,如果分割得到的结果最多有2个,第1个表示x<price,第2个表示price<=y

    2.3.2 代码实现

    修改com.changgou.service.impl.SearchServiceImpl的搜索方法,增加价格区间查询操作,代码如下:

    代码如下:

    @Override
    public Map search(Map<String, String> searchMap) throws Exception {
        Map<String, Object> resultMap = new HashMap<>();
    
        //有条件才查询Es
        if (null != searchMap) {
            //组合条件对象
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            //0:关键词
            if (!StringUtils.isEmpty(searchMap.get("keywords"))) {
                boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
            }
            //1:条件 品牌
            if (!StringUtils.isEmpty(searchMap.get("brand"))) {
                boolQuery.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
            }
    
            //2:条件 规格
            for (String key : searchMap.keySet()) {
                if (key.startsWith("spec_")) {
                    String value = searchMap.get(key).replace("%2B", "+");
                    boolQuery.filter(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword",value));
                }
            }
            //3:条件 价格   0-500  500-1000  5000 如有一个值就打大于,两个值为一个区间
            if (StringUtils.isNotEmpty(searchMap.get("price"))) {
                String[] p = searchMap.get("price").split("-");
               if (p.length == 2) {
                    boolQuery.filter(QueryBuilders.rangeQuery("price").lte(p[1]));
                }  
                boolQuery.filter(QueryBuilders.rangeQuery("price").gte(p[0]));
            }
    
            //4. 原生搜索实现类
            NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
            nativeSearchQueryBuilder.withQuery(boolQuery);
    
            //6. 品牌聚合(分组)查询
            String skuBrand = "skuBrand";
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));
    
            //7. 规格聚合(分组)查询
            String skuSpec = "skuSpec";
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuSpec).field("spec.keyword"));
    
            //10: 执行查询, 返回结果对象
            AggregatedPage<SkuInfo> aggregatedPage = esTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class, new SearchResultMapper() {
                @Override
                public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
    
                    List<T> list = new ArrayList<>();
    
                    SearchHits hits = searchResponse.getHits();
                    if (null != hits) {
                        for (SearchHit hit : hits) {
                            SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
    
                            list.add((T) skuInfo);
                        }
                    }
                    return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
                }
            });
    
            //11. 总条数
            resultMap.put("total", aggregatedPage.getTotalElements());
            //12. 总页数
            resultMap.put("totalPages", aggregatedPage.getTotalPages());
            //13. 查询结果集合
            resultMap.put("rows", aggregatedPage.getContent());
    
            //14. 获取品牌聚合结果
            StringTerms brandTerms = (StringTerms) aggregatedPage.getAggregation(skuBrand);
            List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
            resultMap.put("brandList", brandList);
    
            //15. 获取规格聚合结果
            StringTerms specTerms = (StringTerms) aggregatedPage.getAggregation(skuSpec);
            List<String> specList = specTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
            resultMap.put("specList", specList(specList));
    
            return resultMap;
        }
    
        return null;
    }
    

    price=1-500

    3 搜索分页

    3.1 分页分析

    在这里插入图片描述
    页面需要实现分页搜索,所以我们后台每次查询的时候,需要实现分页。用户页面每次会传入当前页和每页查询多少条数据,当然如果不传入每页显示多少条数据,默认查询30条即可。

    3.2 分页实现

    分页使用PageRequest.of( pageNo- 1, pageSize);实现,第1个参数表示第N页,从0开始,第2个参数表示每页显示多少条,实现代码如下:

    代码如下:

        @Override
        public Map search(Map<String, String> searchMap) throws Exception {
            Map<String, Object> resultMap = new HashMap<>();
    
            //有条件才查询Es
            if (null != searchMap) {
                //组合条件对象
                BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
                //0:关键词
                if (!StringUtils.isEmpty(searchMap.get("keywords"))) {
                    boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
                }
                //1:条件 品牌
                if (!StringUtils.isEmpty(searchMap.get("brand"))) {
                    boolQuery.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
                }
    
                //2:条件 规格
                for (String key : searchMap.keySet()) {
                    if (key.startsWith("spec_")) {
                        String value = searchMap.get(key).replace("%2B", "+");
                        boolQuery.filter(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword",value));
                    }
                }
                //3:条件 价格
                if (!StringUtils.isEmpty(searchMap.get("price"))) {
                    String[] p = searchMap.get("price").split("-");
                    boolQuery.filter(QueryBuilders.rangeQuery("price").gte(p[0]));
                    if (p.length == 2) {
                        boolQuery.filter(QueryBuilders.rangeQuery("price").lte(p[1]));
                    }
                }
    
                //4. 原生搜索实现类
                NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
                nativeSearchQueryBuilder.withQuery(boolQuery);
    
                //6. 品牌聚合(分组)查询
                String skuBrand = "skuBrand";
                nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));
    
                //7. 规格聚合(分组)查询
                String skuSpec = "skuSpec";
                nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuSpec).field("spec.keyword"));
    
    
              
                //开启分页查询
                String pageNum = searchMap.get("pageNum"); //当前页
                String pageSize = searchMap.get("pageSize"); //每页显示多少条
                if (StringUtils.isEmpty(pageNum)){
                    pageNum ="1";
                }
                if (StringUtils.isEmpty(pageSize)){
                    pageSize="30";
                }
                //设置分页
                //第一个参数:当前页 是从0开始
                //第二个参数:每页显示多少条
                nativeSearchQueryBuilder.withPageable(PageRequest.of(Integer.parseInt(pageNum)-1,Integer.parseInt(pageSize)));
    
                //10: 执行查询, 返回结果对象
                AggregatedPage<SkuInfo> aggregatedPage = esTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class, new SearchResultMapper() {
                    @Override
                    public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
    
                        List<T> list = new ArrayList<>();
    
                        SearchHits hits = searchResponse.getHits();
                        if (null != hits) {
                            for (SearchHit hit : hits) {
                                SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
    
                                
                                list.add((T) skuInfo);
                            }
                        }
                        return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
                    }
                });
    
                //11. 总条数
                resultMap.put("total", aggregatedPage.getTotalElements());
                //12. 总页数
                resultMap.put("totalPages", aggregatedPage.getTotalPages());
                //13. 查询结果集合
                resultMap.put("rows", aggregatedPage.getContent());
    
                //14. 获取品牌聚合结果
                StringTerms brandTerms = (StringTerms) aggregatedPage.getAggregation(skuBrand);
                List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
                resultMap.put("brandList", brandList);
    
                //15. 获取规格聚合结果
                StringTerms specTerms = (StringTerms) aggregatedPage.getAggregation(skuSpec);
                List<String> specList = specTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
                resultMap.put("specList", specList(specList));
    
                //16. 返回当前页
                resultMap.put("pageNum", pageNum);
    
                return resultMap;
            }
    
            return null;
        }
    

    pageNum和pageSize可以指定

    4 搜索排序

    4.1 排序分析

    排序这里总共有根据价格排序、根据评价排序、根据新品排序、根据销量排序,排序要想实现非常简单,只需要告知排序的域以及排序方式即可实现。

    价格排序:只需要根据价格高低排序即可,降序价格高->低,升序价格低->高

    评价排序:评价分为好评、中评、差评,可以在数据库中设计3个列,用来记录好评、中评、差评的量,每次排序的时候,好评的比例来排序,当然还要有条数限制,评价条数需要超过N条。

    新品排序:直接根据商品的发布时间或者更新时间排序。

    销量排序:销量排序除了销售数量外,还应该要有时间段限制。

    .2 排序实现

    这里我们不单独针对某个功能实现排序,我们只需要在后台接收2个参数,分别是排序域名字和排序方式,代码如下:

        @Override
        public Map search(Map<String, String> searchMap) throws Exception {
            Map<String, Object> resultMap = new HashMap<>();
    
            //有条件才查询Es
            if (null != searchMap) {
                //组合条件对象
                BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
                //0:关键词
                if (!StringUtils.isEmpty(searchMap.get("keywords"))) {
                    boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
                }
                //1:条件 品牌
                if (!StringUtils.isEmpty(searchMap.get("brand"))) {
                    boolQuery.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
                }
    
                //2:条件 规格
                for (String key : searchMap.keySet()) {
                    if (key.startsWith("spec_")) {
                        String value = searchMap.get(key).replace("%2B", "+");
                        boolQuery.filter(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword",value));
                    }
                }
                //3:条件 价格
                if (!StringUtils.isEmpty(searchMap.get("price"))) {
                    String[] p = searchMap.get("price").split("-");
                    boolQuery.filter(QueryBuilders.rangeQuery("price").gte(p[0]));
                    if (p.length == 2) {
                        boolQuery.filter(QueryBuilders.rangeQuery("price").lte(p[1]));
                    }
                }
    
                //4. 原生搜索实现类
                NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
                nativeSearchQueryBuilder.withQuery(boolQuery);
    
                //6. 品牌聚合(分组)查询
                String skuBrand = "skuBrand";
                nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));
    
                //7. 规格聚合(分组)查询
                String skuSpec = "skuSpec";
                nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuSpec).field("spec.keyword"));
    
                //8: 排序   判断有无传过来值即可
                if (!StringUtils.isEmpty(searchMap.get("sortField"))) {
                    if ("ASC".equals(searchMap.get("sortRule"))) {
                        nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortField")).order(SortOrder.ASC));
                    } else {
    
                        nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortField")).order(SortOrder.DESC));
                    }
    
                }
    
                String pageNum = searchMap.get("pageNum");
                if (null == pageNum) {
                    pageNum = "1";
                }
                //9: 分页
                nativeSearchQueryBuilder.withPageable(PageRequest.of(Integer.parseInt(pageNum) - 1, Page.pageSize));
    
                //10: 执行查询, 返回结果对象
                AggregatedPage<SkuInfo> aggregatedPage = esTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class, new SearchResultMapper() {
                    @Override
                    public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
    
                        List<T> list = new ArrayList<>();
    
                        SearchHits hits = searchResponse.getHits();
                        if (null != hits) {
                            for (SearchHit hit : hits) {
                                SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
                                
                                list.add((T) skuInfo);
                            }
                        }
                        return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
                    }
                });
    
                //11. 总条数
                resultMap.put("total", aggregatedPage.getTotalElements());
                //12. 总页数
                resultMap.put("totalPages", aggregatedPage.getTotalPages());
                //13. 查询结果集合
                resultMap.put("rows", aggregatedPage.getContent());
    
                //14. 获取品牌聚合结果
                StringTerms brandTerms = (StringTerms) aggregatedPage.getAggregation(skuBrand);
                List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
                resultMap.put("brandList", brandList);
    
                //15. 获取规格聚合结果
                StringTerms specTerms = (StringTerms) aggregatedPage.getAggregation(skuSpec);
                List<String> specList = specTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
                resultMap.put("specList", specList(specList));
    
                //16. 返回当前页
                resultMap.put("pageNum", pageNum);
    
                return resultMap;
            }
    
            return null;
        }
    

    sortRule=ASC&sortField=price

    5 高亮显示

    5.1 高亮分析

    在这里插入图片描述
    高亮显示是指根据商品关键字搜索商品的时候,显示的页面对关键字给定了特殊样式,让它显示更加突出,如上图商品搜索中,关键字编程了红色,其实就是给定了红色样式。

    5.2 高亮搜索实现步骤解析

    将之前的搜索换掉,换成高亮搜索,我们需要做3个步骤:

    1.指定高亮域,也就是设置哪个域需要高亮显示
      设置高亮域的时候,需要指定前缀和后缀,也就是关键词用什么html标签包裹,再给该标签样式
    2.高亮搜索实现
    3.将非高亮数据替换成高亮数据
    

    第1点,例如在百度中搜索数据的时候,会有2个地方高亮显示,分别是标题和描述,商城搜索的时候,只是商品名称高亮显示了。而高亮显示其实就是添加了样式,例如<span style="color:red;">笔记本</span>,而其中span开始标签可以称为前缀,span结束标签可以称为后缀。

    第2点,高亮搜索使用ElasticsearchTemplate实现。

    第3点,高亮搜索后,会搜出非高亮数据和高亮数据,高亮数据会加上第1点中的高亮样式,此时我们需要将非高亮数据换成高亮数据即可。例如非高亮:华为笔记本性能超强悍 高亮数据:华为<span style="color:red;"笔记本</span>性能超强悍,将非高亮的换成高亮的,到页面就能显示样式了。

    5.3 高亮代码实现

    删掉之前com.changgou.service.impl.SearchServiceImpl的搜索方法搜索代码,用下面高亮搜索代码替换:

    代码如下:

    @Override
    public Map search(Map<String, String> searchMap) throws Exception {
        Map<String, Object> resultMap = new HashMap<>();
    
        //有条件才查询Es
        if (null != searchMap) {
            //组合条件对象
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            //0:关键词
            if (!StringUtils.isEmpty(searchMap.get("keywords"))) {
                boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
            }
            //1:条件 品牌
            if (!StringUtils.isEmpty(searchMap.get("brand"))) {
                boolQuery.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
            }
    
            //2:条件 规格
            for (String key : searchMap.keySet()) {
                if (key.startsWith("spec_")) {
                    String value = searchMap.get(key).replace("%2B", "+");
                    boolQuery.filter(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword",value));
                }
            }
            //3:条件 价格
            if (!StringUtils.isEmpty(searchMap.get("price"))) {
                String[] p = searchMap.get("price").split("-");
                boolQuery.filter(QueryBuilders.rangeQuery("price").gte(p[0]));
                if (p.length == 2) {
                    boolQuery.filter(QueryBuilders.rangeQuery("price").lte(p[1]));
                }
            }
    
            //4. 原生搜索实现类
            NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
            nativeSearchQueryBuilder.withQuery(boolQuery);
    
            //5:高亮
            HighlightBuilder.Field field = new HighlightBuilder
                .Field("name")
                .preTags("<span style='color:red'>")
                .postTags("</span>");
            nativeSearchQueryBuilder.withHighlightFields(field);
    
            //6. 品牌聚合(分组)查询
            String skuBrand = "skuBrand";
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));
    
            //7. 规格聚合(分组)查询
            String skuSpec = "skuSpec";
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuSpec).field("spec.keyword"));
    
            //8: 排序
            if (!StringUtils.isEmpty(searchMap.get("sortField"))) {
                if ("ASC".equals(searchMap.get("sortRule"))) {
                    nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortField")).order(SortOrder.ASC));
                } else {
    
                    nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortField")).order(SortOrder.DESC));
                }
    
            }
    
            String pageNum = searchMap.get("pageNum");
            if (null == pageNum) {
                pageNum = "1";
            }
            //9: 分页
            nativeSearchQueryBuilder.withPageable(PageRequest.of(Integer.parseInt(pageNum) - 1, Page.pageSize));
    
                   //设置高亮域以及高亮的样式   自己定义前后标签和颜色即可   加完后会在这个nativeSearchQueryBuilder中新的属性
                HighlightBuilder.Field field = new HighlightBuilder.Field("name")//高亮域
                        .preTags("<span style='color:red'>")//高亮样式的前缀
                        .postTags("</span>");//高亮样式的后缀
                nativeSearchQueryBuilder.withHighlightFields(field);
    
    
    
    
        //开启查询
                /**
                 * 第一个参数: 条件构建对象
                 * 第二个参数: 查询操作实体类
                 * 第三个参数: 查询结果操作对象
                 */
                //封装查询结果
                AggregatedPage<SkuInfo> resultInfo = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class, new SearchResultMapper() {
                    @Override
                    public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
                        //查询结果操作
                        List<T> list = new ArrayList<>();
    
                        //获取查询命中结果数据
                        SearchHits hits = searchResponse.getHits();
                        if (hits != null){
                            //有查询结果
                            for (SearchHit hit : hits) {
                                //SearchHit转换为skuinfo
                                SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
    
                                Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                                if (highlightFields != null && highlightFields.size()>0){
                                    //替换数据
                                    skuInfo.setName(highlightFields.get("name").getFragments()[0].toString());
                                }
    
                                list.add((T) skuInfo);
                            }
                        }
                        return new AggregatedPageImpl<T>(list,pageable,hits.getTotalHits(),searchResponse.getAggregations());
                    }
                });
    
            //11. 总条数
            resultMap.put("total", aggregatedPage.getTotalElements());
            //12. 总页数
            resultMap.put("totalPages", aggregatedPage.getTotalPages());
            //13. 查询结果集合
            resultMap.put("rows", aggregatedPage.getContent());
    
            //14. 获取品牌聚合结果
            StringTerms brandTerms = (StringTerms) aggregatedPage.getAggregation(skuBrand);
            List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
            resultMap.put("brandList", brandList);
    
            //15. 获取规格聚合结果
            StringTerms specTerms = (StringTerms) aggregatedPage.getAggregation(skuSpec);
            List<String> specList = specTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
            resultMap.put("specList", specList(specList));
    
            //16. 返回当前页
            resultMap.put("pageNum", pageNum);
    
            return resultMap;
        }
    
        return null;
    }
    
    

    name=手机

    6、最终代码

    package com.changgou.search.service.impl;
    
    import com.alibaba.fastjson.JSON;
    import com.changgou.search.pojo.SkuInfo;
    import com.changgou.search.service.SearchService;
    import org.apache.commons.lang.StringUtils;
    import org.elasticsearch.action.search.SearchResponse;
    import org.elasticsearch.index.query.BoolQueryBuilder;
    import org.elasticsearch.index.query.Operator;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.elasticsearch.search.SearchHit;
    import org.elasticsearch.search.SearchHits;
    import org.elasticsearch.search.aggregations.Aggregation;
    import org.elasticsearch.search.aggregations.AggregationBuilders;
    import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
    import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
    import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
    import org.elasticsearch.search.sort.SortBuilders;
    import org.elasticsearch.search.sort.SortOrder;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.domain.Page;
    import org.springframework.data.domain.PageRequest;
    import org.springframework.data.domain.Pageable;
    import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
    import org.springframework.data.elasticsearch.core.SearchResultMapper;
    import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
    import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
    import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
    import org.springframework.stereotype.Service;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;
    
    @Service
    public class SearchServiceImpl implements SearchService {
    
        @Autowired
        private ElasticsearchTemplate elasticsearchTemplate;
    
        @Override
        public Map search(Map<String, String> searchMap) {
    
            Map<String,Object> resultMap = new HashMap<>();
    
            //构建查询
            if (searchMap != null){
                //构建查询条件封装对象
                NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
                //构建查询对象
                BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
    
                //按照关键字查询
                if (StringUtils.isNotEmpty(searchMap.get("keywords"))){
                    //模糊匹配,类似于%内容%,and做拼接         想买啥
                    boolQuery.must(QueryBuilders.matchQuery("name",searchMap.get("keywords")).operator(Operator.AND));
                }
    
                //按照品牌进行过滤查询
                if (StringUtils.isNotEmpty(searchMap.get("brand"))){
                    //过滤精准匹配,用作查询具体的品牌          指定具体牌子苹果手机
                    boolQuery.filter(QueryBuilders.termQuery("brandName",searchMap.get("brand")));
                }
    
    
                //按照规格进行过滤查询
                for (String key : searchMap.keySet()) {
                    //和前端商量好前缀的格式,加入规格,传过来的参数比如spec_64GB或者颜色等等 比如买64GB大小的,用精准匹配             浏览器地址栏+号会自动转换成%2B
                    if (key.startsWith("spec_")){
                        String value = searchMap.get(key).replace("%2B","+");
                        //spec_网络制式
                        boolQuery.filter(QueryBuilders.termQuery(("specMap."+key.substring(5)+".keyword"),value));
                    }
                }
    
                //按照价格进行区间过滤查询
                if (StringUtils.isNotEmpty(searchMap.get("price"))){
                    String[] prices = searchMap.get("price").split("-");
                    // 0-500 500-1000       如果长度为2加个小于
                    if (prices.length == 2){
                        boolQuery.filter(QueryBuilders.rangeQuery("price").lte(prices[1]));
                    }
                    boolQuery.filter(QueryBuilders.rangeQuery("price").gte(prices[0]));
                }
                //查询条件写完后加入进去进行构建
                nativeSearchQueryBuilder.withQuery(boolQuery);
    
                //按照品牌进行分组(聚合)查询
                String skuBrand="skuBrand";
                nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));
    
                //按照规格进行聚合查询
                String skuSpec="skuSpec";
                nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuSpec).field("spec.keyword"));
    
                //开启分页查询
                String pageNum = searchMap.get("pageNum"); //当前页
                String pageSize = searchMap.get("pageSize"); //每页显示多少条
                if (StringUtils.isEmpty(pageNum)){
                    pageNum ="1";
                }
                if (StringUtils.isEmpty(pageSize)){
                    pageSize="30";
                }
                //设置分页
                //第一个参数:当前页 是从0开始
                //第二个参数:每页显示多少条
                nativeSearchQueryBuilder.withPageable(PageRequest.of(Integer.parseInt(pageNum)-1,Integer.parseInt(pageSize)));
    
                //按照相关字段进行排序查询
                // 1.当前域 2.当前的排序操作(升序ASC,降序DESC)
                if (StringUtils.isNotEmpty(searchMap.get("sortField")) && StringUtils.isNotEmpty(searchMap.get("sortRule"))){
                    if ("ASC".equals(searchMap.get("sortRule"))){
                        //升序
                        nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort((searchMap.get("sortField"))).order(SortOrder.ASC));
                    }else{
                        //降序
                        nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort((searchMap.get("sortField"))).order(SortOrder.DESC));
                    }
                }
    
                //设置高亮域以及高亮的样式   自己定义前后标签和颜色即可   加完后会在这个nativeSearchQueryBuilder中新的属性
                HighlightBuilder.Field field = new HighlightBuilder.Field("name")//高亮域
                        .preTags("<span style='color:red'>")//高亮样式的前缀
                        .postTags("</span>");//高亮样式的后缀
                nativeSearchQueryBuilder.withHighlightFields(field);
    
                //开启查询
                /**
                 * 第一个参数: 条件构建对象
                 * 第二个参数: 查询操作实体类
                 * 第三个参数: 查询结果操作对象
                 */
                //封装查询结果
                AggregatedPage<SkuInfo> resultInfo = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class, new SearchResultMapper() {
                    @Override
                    public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
                        //查询结果操作
                        List<T> list = new ArrayList<>();
    
                        //获取查询命中结果数据
                        SearchHits hits = searchResponse.getHits();
                        if (hits != null){
                            //有查询结果
                            for (SearchHit hit : hits) {
                                //SearchHit转换为skuinfo
                                SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
    
                                Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                                if (highlightFields != null && highlightFields.size()>0){
                                    //替换数据
                                    skuInfo.setName(highlightFields.get("name").getFragments()[0].toString());
                                }
    
                                list.add((T) skuInfo);
                            }
                        }
                        return new AggregatedPageImpl<T>(list,pageable,hits.getTotalHits(),searchResponse.getAggregations());
                    }
                });
    
                //封装最终的返回结果
                //总记录数
                resultMap.put("total",resultInfo.getTotalElements());
                //总页数
                resultMap.put("totalPages",resultInfo.getTotalPages());
                //数据集合
                resultMap.put("rows",resultInfo.getContent());
    
                //封装品牌的分组结果
               StringTerms brandTerms = (StringTerms) resultInfo.getAggregation(skuBrand);
               List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
                resultMap.put("brandList",brandList);
    
                //封装规格分组结果
                StringTerms specTerms= (StringTerms) resultInfo.getAggregation(skuSpec);
                List<String> specList = specTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
                resultMap.put("specList",specList);
    
                //当前页
                resultMap.put("pageNum",pageNum);
                return resultMap;
            }
            return null;
        }
    }
    
    
    展开全文
  • Elasticsearch 实战 - 第四讲:ES 高级查询一、高级查询1、简介2、结果排序3、分页查询4、检索查询5、关键字查询6、高亮显示7、逻辑查询8、过滤查询 一、高级查询 1、简介 Elasticsearch基于JSON提供完整的查询DSL...
  • Elasticsearch查询模式一种是像传递URL参数一样去传递查询语句,被称为简单查询GET /library/books/_search //查询index为library,type为books的全部内容 GET /library/books/_search?q=price:10 //查询index为...
  • es商品模糊查询案例 1,需求及背景: 根据商品编码或者名称模糊查询,之前走数据库,根据名称查询较慢,现在走es 2,代码逻辑: 先判断编码查询还是名称查询查询逻辑不一样 2.1,编码查询查询商品编码或...
  • ES商品查询

    2018-12-21 01:31:26
    首先看前端数据、 前端分页page、必须存在数据,所以需要指定page必须有值。
  • Golang查询Elasticsearch

    万次阅读 热门讨论 2019-02-15 15:33:13
    本文主要讲 golang 连接 es 的常用操作 1.下载依赖库 $ go get github.com/olivere/elastic 操作代码如下: es.go package es import ( "context" "fmt" "github....
  • elasticsearch 是分布式的 "搜索引擎" 和 "数据分析引擎
  • ElasticSearch 查询

    2020-04-17 16:50:00
    ES常用的查询方式如下 term系列 精确搜素 match系列 精确搜索、模糊搜索 exists 指定字段存在(有值) prefix 前缀匹配,只能是keyword类型的字段 wildcard 通配符 regexp 正则表达式匹配 ids 根据id进行查询 term...
  • 2 _validate - 校验查询语句是否合法 3 match query - 匹配查询 3.1 简单功能示例 3.1.1 查询所有文档 3.1.2 查询满足一定条件的文档 3.1.3 分页查询文档 3.1.4 指定返回的结果中包含的字段 3.2 精确查询 - ...
  • elasticsearch实现商品搜索

    千次阅读 2020-03-01 15:20:06
    elasticsearch实现商品搜索 安装es 安装rocketmq 数据同步
  • 本文主要讲解整合Elasticsearch的过程,以实现商品信息在Elasticsearch中的导入、查询、修改、删除为例。 项目使用框架介绍 Elasticsearch Elasticsearch 是一个分布式、可扩展、实时的搜索与数据分析引擎。 它能从...
  • ES入门(四)Elasticsearch之单字符串多字段查询 Dis Max Query 数据准备,索引my_index002 { "name" : "C++", "dec" : "i like writing artcle" }, { "name" : "java", "dec" : "i like writing solution ...
  • 最近做了个小例子:将数据库中数据导入到elasticsearch中,然后查询一条特定数据,看是不是比较快 前情:在数据库中查询数据的速度是3.36s. 用代码从数据库中查出来再插入elasticsearch也许会比较慢,后续再学习用...
  • 目录 1 什么是DSL 2 _validate - 校验查询语句是否合法 3 match query - 匹配查询 3.1 简单功能示例 3.1.1 查询所有文档 3.1.2 查询满足一定条件的文档 3.1.3 分页查询文档 3.1....
  • ElasticSearch 范围查询

    2021-08-29 15:04:29
    ElasticSearch 范围查询 就是 在网页里 搜索像 价格范围 在 2000-3000元内的商品,或 搜索房子面积 在 100 - 150平方的 这些功能。 用来进行:范围查询``的关键字 range # 范围查询 GET goods/_search { "query": ...
  • ElasticSearch高级查询

    2021-04-25 14:57:18
    Elasticsearch在2.x版本的时候把filter查询给摘掉了,因此在query dsl里面已经找不到filter query了。其实es并没有完全抛弃 filter query,而是它的设计与之前的query太重复了。因此直接给转移到了bool查询中。 ...
  • SPL 查询语法 for elasticsearch基本查询全文检索index=es_sql_test* 502 AND Woodard短语查询index=es_sql_test* "502 Baycliff Terrace"字段值查询index=es_sql_test* state=PA AND age<30 AND gender=M逻辑...
  • 技术站:ElasticSearch Spring Data Elasticsearch 索引: 商品信息:micro:products 文档: 商品id 商品名称 商品的关键字 商品的类型 1.数据一致性的保证: a.采用定时任务实现ES数据的维护 从Redis获取数据: 1个...
  • Elasticsearch查询

    2020-11-09 17:20:19
    Elasticsearch查询查询指定字段name和age查询source查询cd1下的所有数据条件过滤查询数据 查询指定字段name和age curl -XGET http://localhost:9200/test/cd1/1/_source=name,age 查询source curl -XGET ...
  • es在数据量很大的情况下(数十亿级别)如何提高查询效率啊? 问这个问题,是肯定的,说白了,就是看你有没有实际干过es,因为啥?es说白了其实性能并没有你想象中那么好的。很多时候数据量大了,特别是有几亿条...
  • elasticsearch Mapping字段映射 映射定义文档如何被存储和检索的 核心数据类型 (1)字符串 text ⽤于全⽂索引,搜索时会自动使用分词器进⾏分词再匹配 keyword 不分词,搜索时需要匹配完整的值 (2)数值型 整型...
  • 通过Elasticsearch完成商品列表查询和分类、品牌、规格参数的分组聚合统计查询 当用户输入关键字搜索后,查询商品列表后点击右上角筛选按钮,能够展示该关键词下的全部商品分类(京东用的是二级分类,共3级分类...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 10,726
精华内容 4,290
关键字:

商品查询es