当前位置: 首页 > news >正文

谷粒商城のElasticsearch

文章目录

  • 前言
  • 一、前置知识
    • 1、Elasticsearch 的结构
    • 2、倒排索引 (Inverted Index)
      • 2.1、 索引阶段
      • 2.2、查询阶段
  • 二、环境准备
    • 1、安装Es
    • 2、安装Kibana
    • 3、安装 ik 分词器
  • 三、项目整合
    • 1、引入依赖
    • 2、整合业务
      • 2.1、创建索引、文档、构建查询语句
      • 2.2、整合业务代码
  • 后记


前言

  本篇介绍谷粒商城项目检索服务,从搭建es环境到商城检索业务的实现。(不考虑freeMarker模版中的jquery部分)
  对应视频:P173-P192


一、前置知识

1、Elasticsearch 的结构

  同传统的关系型数据库进行类比:

  1. 索引 (Index):相当于关系数据库中的表,由多个文档组成。
  2. 文档 (Document):文档是 Elasticsearch 中存储的基本数据单位,相当于关系数据库中的一行。文档以 JSON 格式存储。每个文档属于一个特定的索引,并具有唯一的 ID。
  3. 映射 (Mapping):类似于数据库中的表结构定义,定义了文档的字段及其数据类型。(DDL建表语句)

2、倒排索引 (Inverted Index)

  在倒排索引中,每个词项都关联到一个倒排列表(Posting List),该列表存储着包含这个词项的所有文档的 ID。倒排索引的构建和查询主要分为以下两个阶段:

2.1、 索引阶段

  当文档被添加到系统中时,首先会进行文档解析(分词),然后将每个词项添加到倒排索引的词典中。词典存储的是文档中所有唯一词项的列表。最后对于每个词项,记录该词项在哪些文档中出现,存储这些文档的 ID 以及该词项在文档中的位置(可选)。这些信息称为倒排列表。
  例如我现在有两个文档:文档一:布偶猫吃鱼和文档二:加菲猫吃鱼,分词后可以得到以下倒排索引(假设目前使用的是ik分词器):

文档ID
布偶1
加菲猫2
1,2
吃鱼1,2

2.2、查询阶段

  首先会进行分词,布偶猫吃鱼会得到词项【布偶吃鱼】,加菲猫吃鱼会得到词项【加菲猫吃鱼】。然后根据分出的词去查找其文档ID:

  • 布偶的文档ID是1。
  • 的文档ID是1,2。
  • 吃鱼的文档ID是1,2。
  • 加菲猫的文档ID是2。
      最后找到同时包含所有查询词项的文档,例如我要搜索布偶猫吃鱼,文档1会作为结果返回。

二、环境准备

1、安装Es

  在本项目中采用docker安装es的方式。es和kibana均采用7.4.2版本
  首先执行:

mkdir -p /mydata/elasticsearch/config
mkdir -p /mydata/elasticsearch/data

  然后执行:

echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml

  这条命令的含义是,在 /mydata/elasticsearch/config/elasticsearch.yml 文件的末尾添加一行配置,内容是http.host: 0.0.0.0,会允许服务通过任何 IP 地址访问Elasticsearch 。

chmod -R 777 /mydata/elasticsearch/

  这条命令的含义是,修改指定目录下的所有文件和子目录的权限。777 表示赋予所有用户(文件的所有者、同组用户、其他用户)读取(r)写入(w) 执行(x) 的权限。(权限的 777 是通过组合 rwx(读、写、执行)的权限位来得到的:7 = r + w + x = 4 + 2 + 1,即读、写、执行权限都有。)
  最后执行:

docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms64m -Xmx512m" \
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
-v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.4.2

- docker run:启动一个新的 Docker 容器。
---name elasticsearch:给容器命名为 elasticsearch。
--p 9200:9200 -p 9300:9300:-p 选项用于将主机的端口与容器内的端口进行映射。9200:9200:将主机的 9200 端口映射到容器的 9200 端口(Elasticsearch 默认的 HTTP REST API 端口)。9300:9300:将主机的 9300 端口映射到容器的 9300 端口(Elasticsearch 默认的内部节点通信端口)。
- -e "discovery.type=single-node":-e 选项用于设置环境变量。“discovery.type=single-node”:指定 Elasticsearch 以单节点模式运行,即不需要集群节点的发现(通常用于开发或测试环境)。
--e ES_JAVA_OPTS="-Xms64m -Xmx512m":ES_JAVA_OPTS 是用于配置 JVM 的选项。-Xms64m:设置 JVM 的最小堆内存为 64MB。-Xmx512m:设置 JVM 的最大堆内存为 512MB。
--v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml:-v 选项用于将主机上的目录或文件挂载到容器中。将主机上 /mydata/elasticsearch/config/elasticsearch.yml 文件挂载到容器内的 /usr/share/elasticsearch/configelasticsearch.yml,用于替换容器中的默认配置文件。
- -v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml:-v 选项用于将主机上的目录或文件挂载到容器中。将主机上/mydata/elasticsearch/config/elasticsearch.yml文件挂载到容器内的 /usr/share/elasticsearch/config/elasticsearch.yml,用于替换容器中的默认配置文件。
--v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins:将主机上的/mydata/elasticsearch/plugins目录挂载到容器的/usr/share/elasticsearch/plugins目录,用于存储和管理 Elasticsearch 插件。
- -d elasticsearch:7.4.2:-d 选项让容器在后台运行(守护模式)。elasticsearch:7.4.2:指定要使用的 Elasticsearch 镜像及其版本号 7.4.2。

2、安装Kibana

  Kibana相当于es的可视化界面和控制台:

docker run --name kibana -e ELASTICSEARCH_HOSTS=http://自己的虚拟机地址:9200 -p 5601:5601 \
-d kibana:7.4.2

- docker run:启动一个新的 Docker 容器。
---name kibana: 给容器命名为kibana。
-e ELASTICSEARCH_HOSTS=http://192.168.56.10:9200:指定 Kibana 连接的 Elasticsearch 集群地址为 http://192.168.56.10:9200。
-p 5601:5601:5601:5601:将主机的 5601 端口映射到容器的 5601 端口。
-d kibana:7.4.2:指定使用 7.4.2 版本的 Kibana 镜像,并使容器在后台运行。

3、安装 ik 分词器

  将ik分词器拷贝到/mydata/elasticsearch/plugins/ik/下,重启es容器,可使用下面的命令验证是否安装成功:

curl -X GET "http://localhost:9200/_cat/plugins?v"

在这里插入图片描述


三、项目整合

1、引入依赖

  在gulimall-search模块中引入依赖:

<dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId><version>7.4.2</version>
</dependency>
<dependency><groupId>org.elasticsearch</groupId><artifactId>elasticsearch</artifactId><version>7.4.2</version>
</dependency>
<dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-client</artifactId><version>7.4.2</version>
</dependency>

  后续在项目中使用,只需要注入RestHighLevelClient即可

@Autowired
private RestHighLevelClient client;

2、整合业务

2.1、创建索引、文档、构建查询语句

  在分布式基础篇的后台管理系统中,点击上架,会将商品信息保存在gulimall_product索引中,gulimall_product的映射:(注意,分布式基础篇中,映射是product,和现在的gulimall_product有所不同,需要进行数据迁移)

{"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"}}}}
}

  数据迁移:

# 迁移数据
POST _reindex
{"source": {"index": "product"},"dest": {"index": "gulimall_product"}
}

  在页面上点击搜索时,相当于带着搜索条件去ES中进行检索,并且将检索的结果封装成对象返回给前端页面进行展示,我们需要:

  1. skuTitle进行模糊匹配,高亮显示。
  2. catalogIdbrandIdattrshasStockrange进行过滤。
  3. skuPrice进行排序。
  4. 进行分页。
  5. 根据上面查询的结果进行聚合分析,按照brandIdcatalogIdattrId,查询出共有的部分。
      其中mustfilter的区别:must 中的条件会参与相关性评分(_score)的计算。如果多个条件都出现在 must 中,满足的条件越多,相关性评分就越高。filter 只用于过滤文档,满足条件的文档会返回,但不会参与相关性评分的计算。因此它比 must 更高效,特别适合用于不需要计算评分的精确匹配或过滤。
      什么是相关性评分?
      相关性评分是 Elasticsearch 在返回搜索结果时,用来衡量每个文档与查询条件的匹配程度的一个分值。每个文档返回时都有一个 _score 值,表示这个文档与查询的匹配程度。分数越高,意味着这个文档与查询条件越相关。
      构建的查询语句:
GET /gulimall_product/_search
{"query": {"bool": {"must": [{"match": {"skuTitle": "iphone"}}],"filter": [{"term": {"catalogId": {"value": "225"}}},{"terms": {"brandId": ["8","9"]}},{"nested": {"path": "attrs","query": {"bool": {"must": [{"term": {"attrs.attrId": {"value": "1"}}},{"terms": {"attrs.attrValue": ["5G","4G"]}}]}}}},{"term": {"hasStock": {"value": "false"}}},{"range": {"skuPrice": {"gte": 4999,"lte": 5400}}}]}},"sort": [{"skuPrice": {"order": "desc"}}],"from": 0,"size": 10,"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": "catelogName","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}}}}}}}
}

  对于其中一些关键点的解释:

  • nested:用于处理文档中的嵌套字段嵌套字段指的是ES文档中存储的对象类型的数据,而普通的对象的ES中会被扁平化处理,可能会导致错误匹配的现象。而定义一个字段为嵌套类型时,Elasticsearch 会将每个嵌套对象视为独立的小文档,但它仍然与父文档保持关联。
#定义嵌套字段
{"mappings": {"properties": {"attrs": {"type": "nested", "properties": {"attrId": { "type": "integer" },"attrValue": { "type": "text" }}}}}
}```bash
# 查询嵌套字段
{"nested": {"path": "attrs","query": {"bool": {"must": [{ "term": { "attrs.attrId": 1 } },{ "term": { "attrs.attrValue": "红色" } }]}}}
}
  • aggs:是一种用于计算统计信息、汇总数据的功能。可以对查询到的结果进行分析、分组、统计等操作,aggs还可以嵌套使用,即在一个聚合内部定义另一个聚合:
#先根据 brandId 字段对文档进行分组,然后对每个 brandId 组计算该组中文档的 price 字段的平均值。
{"aggs": {"brand_agg": {"terms": {"field": "brandId"},"aggs": {"avg_price": {"avg": {"field": "price"}}}}}
}

而对于嵌套字段的聚合,需要进行特殊处理:

# 首先对嵌套字段 attrs 进行聚合,然后对 attrs.attrId 进行 terms 聚合
{"aggs": {"nested_attrs": {"nested": {"path": "attrs"},"aggs": {"attr_id": {"terms": {"field": "attrs.attrId"}}}}}
}

2.2、整合业务代码

  到这里查询语句就已经构建完毕了,但是还需要将查询语句转化成Java语言,利用RestHighLevelClient发送请求进行查询并且解析返回结果:

    /*** 商品上架后信息保存在es->根据前端传递的搜索条件构建dsl去es中搜索->解析并封装查询结果给前端页面* @param searchDTO 条件* @return*/@Overridepublic SearchVO searchForCondition(SearchDTO searchDTO) {//构建查询请求SearchRequest searchRequest = this.getSearchRequest(searchDTO);SearchVO vo;try {//查询SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);System.out.println("返回的结果"+searchResponse.toString());//解析查询结果,封装成SearchVOvo = this.parseSearchRequest(searchResponse,searchDTO);} catch (Exception e) {throw new RuntimeException(e);}return vo;}

  查询:

    private SearchRequest getSearchRequest(SearchDTO searchDTO) {// 构建查询SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();// Bool查询BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();// 模糊匹配 skuTitlethis.likeSearch(searchDTO, boolQuery);//过滤查询this.filter(searchDTO, boolQuery, searchSourceBuilder);//分页高亮排序this.sortPageAndHighlight(searchDTO, searchSourceBuilder);//对搜索的结果进行聚合分析this.termsAggregation(searchSourceBuilder);//测试 打印构建结果String s = searchSourceBuilder.toString();System.out.println("构建的 DSL" + s);//创建搜索请求SearchRequest searchRequest = new SearchRequest(newString[]{ESConstants.PRODUCT_INDEX}, searchSourceBuilder);return searchRequest;}

  标题模糊匹配:

    private void likeSearch(SearchDTO searchDTO, BoolQueryBuilder boolQuery) {String keyword = searchDTO.getKeyword();if (StringUtils.isNotBlank(keyword)) {boolQuery.must(QueryBuilders.matchQuery("skuTitle", keyword)); //对应es中的must}}

  过滤查询:

    private void filter(SearchDTO searchDTO, BoolQueryBuilder boolQuery, SearchSourceBuilder searchSourceBuilder) {// catalogId 查询Long catalog3Id = searchDTO.getCatalog3Id();if (!ObjectUtils.isEmpty(catalog3Id)) {boolQuery.filter(QueryBuilders.termsQuery("catalogId", new long[]{catalog3Id})); //对应es中的filter}//bool - filter - 按照品牌 id 查询List<Long> brandId = searchDTO.getBrandId();if (!CollectionUtils.isEmpty(brandId)) {boolQuery.filter(QueryBuilders.termsQuery("brandId",brandId));}//bool - filter - 按照所有指定的属性进行查询List<String> attrs = searchDTO.getAttrs();if (!CollectionUtils.isEmpty(attrs)) {BoolQueryBuilder nestedboolQuery = QueryBuilders.boolQuery(); //对应es中的nested//进行处理//attr=1_5寸:8寸for (String attr : attrs) {String attrId = attr.split("_")[0];String value = attr.split("_")[1];
//                String[] attrValues = s.split(":");nestedboolQuery.must(QueryBuilders.termQuery("attrs.attrId",attrId));nestedboolQuery.must(QueryBuilders.termsQuery("attrs.attrValue",value));//每一个必须都得生成一个 nested 查询NestedQueryBuilder nestedQuery =QueryBuilders.nestedQuery("attrs", nestedboolQuery, ScoreMode.None);boolQuery.filter(nestedQuery);}}//bool - filter - 按照库存是否有进行查询if (searchDTO.getHasStock() != null) {boolQuery.filter(QueryBuilders.termQuery("hasStock",searchDTO.getHasStock() == 1));}//1.2、bool - filter - 按照价格区间if (!StringUtils.isEmpty(searchDTO.getSkuPrice())) {//1_500/_500/500_/*** "range": {* "skuPrice": {* "gte": 0,* "lte": 6000* }* }*/RangeQueryBuilder rangeQuery =QueryBuilders.rangeQuery("skuPrice"); //对应es中的rangeString[] s = searchDTO.getSkuPrice().split("_");if (s.length == 2) {//区间rangeQuery.gte(s[0]).lte(s[1]);} else if (s.length == 1) {if (searchDTO.getSkuPrice().startsWith("_")) {rangeQuery.lte(s[0]);}if (searchDTO.getSkuPrice().endsWith("_")) {rangeQuery.gte(s[0]);}}boolQuery.filter(rangeQuery);}//把以前的所有条件都拿来进行封装searchSourceBuilder.query(boolQuery);}

  分页高亮排序:

    private void sortPageAndHighlight(SearchDTO searchDTO, SearchSourceBuilder searchSourceBuilder) {//排序//sort=hotScore_asc/descString sort = searchDTO.getSort();if (StringUtils.isNotBlank(sort)) {String sortStr = sort.split("_")[1];SortOrder order = sortStr.equalsIgnoreCase("asc") ? SortOrder.ASC : SortOrder.DESC;searchSourceBuilder.sort(sort.split("_")[0], order);}//分页searchSourceBuilder.from((searchDTO.getPageNum() - 1) *ESConstants.PRODUCT_PAGESIZE);searchSourceBuilder.size(ESConstants.PRODUCT_PAGESIZE);//高亮if (!StringUtils.isEmpty(searchDTO.getKeyword())) {HighlightBuilder builder = new HighlightBuilder();builder.field("skuTitle");builder.preTags("<b style='color:red'>");builder.postTags("</b>");searchSourceBuilder.highlighter(builder);}}

  聚合分析:

    private void termsAggregation(SearchSourceBuilder searchSourceBuilder) {/*** 聚合分析*///1、品牌聚合TermsAggregationBuilder brand_agg =AggregationBuilders.terms("brand_agg");brand_agg.field("brandId").size(50);//品牌聚合的子聚合brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(1));brand_agg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(1));/*1、聚合 brand*/searchSourceBuilder.aggregation(brand_agg);//2、分类聚合 catalog_aggTermsAggregationBuilder catalog_agg =AggregationBuilders.terms("catalog_agg").field("catalogId").size(20);catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));/*2、聚合 catalog*/searchSourceBuilder.aggregation(catalog_agg);//3、属性聚合 attr_aggNestedAggregationBuilder attr_agg =AggregationBuilders.nested("attr_agg", "attrs");//聚合出当前所有的 attrIdTermsAggregationBuilder attr_id_agg =AggregationBuilders.terms("attr_id_agg").field("attrs.attrId");//聚合分析出当前 attr_id 对应的名字attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));//聚合分析出当前 attr_id 对应的所有可能的属性值 attrValueattr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));attr_agg.subAggregation(attr_id_agg);/*3、聚合 attr*/searchSourceBuilder.aggregation(attr_agg);}

  解析返回结果:

    private SearchVO parseSearchRequest(SearchResponse resp, SearchDTO searchDTO) {SearchVO vo = new SearchVO();//外层hitsSearchHits hits = resp.getHits();//里层hitsSearchHit[] hitsArr = hits.getHits();List<ESPojo> esPojos = Arrays.stream(hitsArr).map(searchHit -> {//_sourceString sourceAsString = searchHit.getSourceAsString();ESPojo esPojo = JSON.parseObject(sourceAsString, ESPojo.class);//判断是否按关键字检索,若是就显示高亮,否则不显示if (!StringUtils.isEmpty(searchDTO.getKeyword())) {//拿到高亮信息显示标题HighlightField skuTitle = searchHit.getHighlightFields().get("skuTitle");String skuTitleValue = skuTitle.getFragments()[0].string();esPojo.setSkuTitle(skuTitleValue);}return esPojo;}).collect(Collectors.toList());//返回的所有商品vo.setProducts(esPojos);//聚合信息ParsedLongTerms brandAgg = resp.getAggregations().get("brand_agg");List<? extends Terms.Bucket> brandAggBuckets = brandAgg.getBuckets();List<SearchVO.BrandsVO> brandsVOS = brandAggBuckets.stream().map(bucket -> {SearchVO.BrandsVO brandsVO = new SearchVO.BrandsVO();//得到品牌idlong brandId = bucket.getKeyAsNumber().longValue();brandsVO.setBrandId(brandId);//得到品牌名字ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brand_name_agg");String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();brandsVO.setBrandName(brandName);//3、得到品牌的图片ParsedStringTerms brandImgAgg = bucket.getAggregations().get("brand_img_agg");String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();brandsVO.setBrandImg(brandImg);return brandsVO;}).collect(Collectors.toList());//封装品牌信息vo.setBrands(brandsVOS);ParsedLongTerms catalogAgg = resp.getAggregations().get("catalog_agg");List<? extends Terms.Bucket> catalogAggBuckets = catalogAgg.getBuckets();List<SearchVO.CatalogsVO> catalogsVOS = catalogAggBuckets.stream().map(bucket -> {SearchVO.CatalogsVO catalogsVO = new SearchVO.CatalogsVO();//得到分类ID//得到品牌idlong catelogId = bucket.getKeyAsNumber().longValue();catalogsVO.setCatalogId(catelogId);//得到品牌名称ParsedStringTerms catalogNameAgg = bucket.getAggregations().get("catalog_name_agg");String catalogName = catalogNameAgg.getBuckets().get(0).getKeyAsString();catalogsVO.setCatalogName(catalogName);return catalogsVO;}).collect(Collectors.toList());//封装分类信息vo.setCatalogs(catalogsVOS);ParsedNested attrsAgg = resp.getAggregations().get("attr_agg");ParsedLongTerms attrIdAgg = attrsAgg.getAggregations().get("attr_id_agg");List<? extends Terms.Bucket> attrsAggBuckets = attrIdAgg.getBuckets();List<SearchVO.AttrsVo> attrsVoList = attrsAggBuckets.stream().map(bucket -> {SearchVO.AttrsVo attrsVo = new SearchVO.AttrsVo();//属性IDlong attrId = bucket.getKeyAsNumber().longValue();attrsVo.setAttrId(attrId);//属性名称ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attr_name_agg");attrsVo.setAttrName(attrNameAgg.getBuckets().get(0).getKeyAsString());//属性值ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attr_value_agg");List<String> attrValues = attrValueAgg.getBuckets().stream().map(item -> item.getKeyAsString()).collect(Collectors.toList());attrsVo.setAttrValue(attrValues);return attrsVo;}).collect(Collectors.toList());//封装属性信息vo.setAttrs(attrsVoList);//封装分页参数vo.setPageNum(searchDTO.getPageNum());int total = (int) resp.getHits().getTotalHits().value;vo.setTotal(total);int totalPages = (int)total % ESConstants.PRODUCT_PAGESIZE == 0 ?(int)total / ESConstants.PRODUCT_PAGESIZE : ((int)total / ESConstants.PRODUCT_PAGESIZE + 1);vo.setTotalPages(totalPages);return vo;}

  到这里为止,构建查询语句,查询ES,解析返回结果的操作就完成了。在高亮展示这一块,有一个很坑的点:如果在对标题进行匹配时,是这样写的:boolQuery.must(QueryBuilders.matchQuery("skuTitle", keyword)).fuzziness(Fuzziness.AUTO)); 实测高亮展示会失效。因为.fuzziness(Fuzziness.AUTO)是模糊匹配的一个配置选项,表示自动确定模糊匹配的程度。下面简单说一下它的工作机制:
  对于长度较短的词(1到2个字符),不进行模糊匹配。对于长度为3到5个字符的词,允许最多一个字符不同。也就是说,输入的词和索引中的词之间最多可以有一个字符的差异。对于长度超过5个字符的词,允许最多两个字符不同,即输入的词和索引中的词之间可以有两处字符差异。
  为什么加上了会使高亮展示失效?因为模糊匹配会容忍一定的字符变化,比如拼写错误或词形变化。高亮显示依赖于精确匹配的词,只有当查询中的词语与索引中的词精确匹配时,ES才会高亮显示。

后记

  本篇主要介绍了ES的组成和基本概念,以及环境搭建,项目业务整合ES。因为项目的重点是后端的逻辑,所以前端模板页面的改造没有写入本篇。在做这个项目之前,去年有曾经专门去看过某马的关于ES的专题教学视频,语法介绍的很详细,当时还跟着敲了一遍。但是工作中至今未遇到使用场景,在做这个项目的时候不出意外地发现几乎全部遗忘了。在这里想说的是,语法并非重点,重点是理解各自项目的业务逻辑,做好笔记,能根据案例和实际的业务场景举一反三。

  下一篇:认证服务

相关文章:

谷粒商城のElasticsearch

文章目录 前言一、前置知识1、Elasticsearch 的结构2、倒排索引 (Inverted Index)2.1、 索引阶段2.2、查询阶段 二、环境准备1、安装Es2、安装Kibana3、安装 ik 分词器 三、项目整合1、引入依赖2、整合业务2.1、创建索引、文档、构建查询语句2.2、整合业务代码 后记 前言 本篇介…...

排队免单模式小程序开发

开发一个排队免单模式的小程序涉及多个方面&#xff0c;包括需求分析、界面设计、后端开发、数据库设计以及测试上线等。下面我将详细介绍每个步骤的概要&#xff1a; 1.需求分析 明确目标&#xff1a;首先确定小程序的核心功能&#xff0c;即排队免单模式的具体实现方式。例如…...

从OracleCloudWorld和财报看Oracle的转变

2024年9月9-12日Oracle Cloud World在美国拉斯维加斯盛大开幕 押注AI和云 Oracle 创始人Larry Ellison做了对Oracle战略和未来愿景的主旨演讲&#xff0c;在演讲中Larry将AI技术和云战略推到了前所未有的高度&#xff0c;从新的Oracle 23c改名到Oracle23ai&#xff0c;到Oracl…...

搭建 PHP

快速搭建 PHP 环境指南 PHP 是一种广泛用于 Web 开发的后端脚本语言&#xff0c;因其灵活性和易用性而受到开发者的青睐。无论是开发个人项目还是企业级应用&#xff0c;PHP 环境的搭建都是一个不可忽视的基础步骤。本指南将带您快速学习如何在不同平台上搭建 PHP 环境&#x…...

kubernetes技术详解,带你深入了解k8s

目录 一、Kubernetes简介 1.1 容器编排应用 1.2 Kubernetes简介 1.3 k8s的设计架构 1.3.1 k8s各个组件的用途 1.3.2 k8s各组件之间的调用关系 1.3.3 k8s的常用名词概念 1.3.4 k8s的分层结构 二、k8s集群环境搭建 2.1 k8s中容器的管理方式 2.2 k8s环境部署 2.2.1 禁用…...

Gateway学习笔记

目录 介绍&#xff1a; 核心概念 依赖 路由 断言 基本的断言工厂 自定义断言 过滤器 路由过滤器 过滤器工厂 自定义路由过滤器 全局过滤器 其他 过滤器执行顺序 前置后置&#xff08;&#xff1f;&#xff09; 跨域问题 yaml 解决 配置类解决 介绍&#x…...

创造增强叙事的互动:Allison Crank的沉浸式体验设计理念

在沉浸式技术日新月异的今天,如何通过用户交互增强叙事,而非分散注意力,成为了设计师们共同面临的挑战。作为用户体验设计师和研究员,Allison Crank以其独特的视角和丰富的经验,为我们揭示了这一领域的核心原则与实践方法。 叙事与互动的和谐共生 Allison Crank强调,互…...

Requests-HTML模块怎样安装和使用?

要安装和使用Requests-HTML模块&#xff0c;您可以按照以下步骤进行操作&#xff1a; 打开命令行界面&#xff08;如Windows的命令提示符或Mac的终端&#xff09;。 使用pip命令安装Requests-HTML模块。在命令行中输入以下命令并按回车键执行&#xff1a; pip install request…...

[网络]从零开始的计算机网络基础知识讲解

一、本次教程的目的 本次教程我只会带大叫了解网络的基础知识&#xff0c;了解网络请求的基本原理&#xff0c;为后面文章中可能会用到网络知识做铺垫。本次我们只会接触到网络相关的应用层&#xff0c;并不涉及协议的具体实现和数据转发的规则。也就是说&#xff0c;这篇教程是…...

wifiip地址可以随便改吗?wifi的ip地址怎么改变

对于普通用户来说&#xff0c;WiFi IP地址的管理和修改往往显得神秘而复杂。本文旨在深入探讨WiFi IP地址是否可以随意更改&#xff0c;以及如何正确地改变WiFi的IP地址。虎观代理小二将详细解释WiFi IP地址的基本概念、作用以及更改时需要注意的事项&#xff0c;帮助用户更好地…...

黑马十天精通MySQL知识点

一. MySQL概述 安装使用 MySQL安装完成之后&#xff0c;在系统启动时&#xff0c;会自动启动MySQL服务&#xff0c;无需手动启动。 也可以手动的通过指令启动停止&#xff0c;以管理员身份运行cmd&#xff0c;进入命令行执行如下指令&#xff1a; 1 、 net start mysql80…...

如何在 Vue 3 + Element Plus 项目中实现动态设置主题色以及深色模式切换

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、引言二、项目依赖和环境配置1. VueUse2. use-element-plus-theme3. 安装依赖 三、实现深色模式切换1. 设置深色模式状态2. 模板中的深色模式切换按钮3. 深色模式的效果展示 四、动态切换主题色五、总结 一、引言 在现代…...

Android 如何实现搜索功能:本地搜索?数据模型如何设计?数据如何展示和保存?

目录 效果图为什么需要搜索功能如何设计搜索本地的功能&#xff0c;如何维护呢&#xff1f;总结 一、效果图 二、为什么需要搜索功能 找一个选项&#xff0c;需要花非常多的时间&#xff0c;并且每次都需要指导客户在哪里&#xff0c;现在只要让他们搜索一下就可以。这也是模…...

【K230 实战项目】气象时钟

【CanMV K230 AI视觉】 气象时钟 功能描述&#xff1a;说明HMDI资源3.5寸屏幕 使用方法 为了方便小伙伴们理解&#xff0c;请查看视频 B站连接 功能描述&#xff1a; 天气信息获取&#xff1a;通过连接到互联网&#xff0c;实时获取天气数据&#xff0c;包括温度、湿度、天气状…...

什么是 HTTP/3?下一代 Web 协议

毫无疑问&#xff0c;发展互联网底层的庞大协议基础设施是一项艰巨的任务。 HTTP 的下一个主要版本基于 QUIC 协议构建&#xff0c;并有望提供更好的性能和更高的安全性。 以下是 Web 应用程序开发人员需要了解的内容。 HTTP/3 的前景与风险 HTTP/3 致力于让互联网对每个人…...

IDEA Project不显示/缺失文件

问题&#xff1a;侧边栏project 模式下缺少部分文件 先点close project 打开项目所在目录&#xff0c;删除目录下的.idea文件夹 重新open project打开这个项目即可解决...

浅谈vue2.0与vue3.0的区别(整理十六点)

目录 1. 实现数据响应式的原理不同 2. 生命周期不同 3. vue 2.0 采用了 option 选项式 API&#xff0c;vue 3.0 采用了 composition 组合式 API 4. 新特性编译宏 5. 父子组件间双向数据绑定 v-model 不同 6. v-for 和 v-if 优先级不同 7. 使用的 diff 算法不同 8. 兄弟组…...

深入理解 MySQL MVCC:多版本并发控制的核心机制

在数据库领域&#xff0c;并发控制是确保多个事务能够正确地并发执行而不破坏数据完整性的关键技术。MySQL 作为广泛使用的关系型数据库管理系统&#xff0c;采用了多版本并发控制&#xff08;Multi-Version Concurrency Control&#xff0c;MVCC&#xff09;机制来实现高效的并…...

Qt6编译达梦8数据库驱动插件

一、编译环境 操作系统&#xff1a;deepin V23 Qt版本&#xff1a; Qt 6.7.2 编译器&#xff1a;gcc/g version 12.3.0&#xff0c;cmake 3.28.3 达梦数据库&#xff1a;开发版V8 二、下载达梦QT接口源码 下载链接&#xff1a; https://eco.dameng.com/downlo…...

什么是机器学习力场

机器学习力场&#xff08;Machine Learning Force Fields, MLFF&#xff09;方法是一类将机器学习技术应用于分子动力学&#xff08;Molecular Dynamics, MD&#xff09;模拟的技术。它通过使用机器学习算法拟合原子之间的相互作用能量和力场&#xff0c;使得在不牺牲精度的前提…...

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…...

Flask RESTful 示例

目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题&#xff1a; 下面创建一个简单的Flask RESTful API示例。首先&#xff0c;我们需要创建环境&#xff0c;安装必要的依赖&#xff0c;然后…...

聊聊 Pulsar:Producer 源码解析

一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台&#xff0c;以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中&#xff0c;Producer&#xff08;生产者&#xff09; 是连接客户端应用与消息队列的第一步。生产者…...

电脑插入多块移动硬盘后经常出现卡顿和蓝屏

当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时&#xff0c;可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案&#xff1a; 1. 检查电源供电问题 问题原因&#xff1a;多块移动硬盘同时运行可能导致USB接口供电不足&#x…...

return this;返回的是谁

一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请&#xff0c;不同级别的经理有不同的审批权限&#xff1a; // 抽象处理者&#xff1a;审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...

规则与人性的天平——由高考迟到事件引发的思考

当那位身着校服的考生在考场关闭1分钟后狂奔而至&#xff0c;他涨红的脸上写满绝望。铁门内秒针划过的弧度&#xff0c;成为改变人生的残酷抛物线。家长声嘶力竭的哀求与考务人员机械的"这是规定"&#xff0c;构成当代中国教育最尖锐的隐喻。 一、刚性规则的必要性 …...

内窥镜检查中基于提示的息肉分割|文献速递-深度学习医疗AI最新文献

Title 题目 Prompt-based polyp segmentation during endoscopy 内窥镜检查中基于提示的息肉分割 01 文献速递介绍 以下是对这段英文内容的中文翻译&#xff1a; ### 胃肠道癌症的发病率呈上升趋势&#xff0c;且有年轻化倾向&#xff08;Bray等人&#xff0c;2018&#x…...

python打卡day49@浙大疏锦行

知识点回顾&#xff1a; 通道注意力模块复习空间注意力模块CBAM的定义 作业&#xff1a;尝试对今天的模型检查参数数目&#xff0c;并用tensorboard查看训练过程 一、通道注意力模块复习 & CBAM实现 import torch import torch.nn as nnclass CBAM(nn.Module):def __init__…...

Electron简介(附电子书学习资料)

一、什么是Electron&#xff1f; Electron 是一个由 GitHub 开发的 开源框架&#xff0c;允许开发者使用 Web技术&#xff08;HTML、CSS、JavaScript&#xff09; 构建跨平台的桌面应用程序&#xff08;Windows、macOS、Linux&#xff09;。它将 Chromium浏览器内核 和 Node.j…...

【AI News | 20250609】每日AI进展

AI Repos 1、OpenHands-Versa OpenHands-Versa 是一个通用型 AI 智能体&#xff0c;通过结合代码编辑与执行、网络搜索、多模态网络浏览和文件访问等通用工具&#xff0c;在软件工程、网络导航和工作流自动化等多个领域展现出卓越性能。它在 SWE-Bench Multimodal、GAIA 和 Th…...