Elasticsearch 是一个使用 Java 语言编写、遵守 Apache 协议、支持 RESTful 风格的分布式全文搜索和分析引擎,它基于 Lucene 库构建,并提供多种语言的 API。Elasticsearch 可以对任何类型的数据进行索引、查询和聚合分析,无论是文本、数字、地理空间、结构化还是非结构化的。
Elasticsearch 的核心功能是搜索,它可以对数据进行分词匹配、相关性评分、高亮显示等操作,返回相关度高的结果列表。Elasticsearch 也可以用作数据分析,它可以对数据进行统计、分类、聚类等操作,返回聚合结果或图表。
本文将用我开源的 waynboot-mall 项目作于代码讲解,Elasticsearch 版本是 7.10.1。
waynboot-mall 是一套全部开源的微商城项目,包含三个项目:运营后台、H5 商城和后端接口。实现了一套完整的商城业务,有首页展示、商品分类、商品详情、sku 详情、商品搜索、加入购物车、结算下单、支付宝/微信支付、订单列表、商品评论等一系列功能。
本文大纲如下,
图片
Elasticsearch 的典型应用场景有以下几种:
waynboot-mall 商城选择使用 Elasticsearch 作为搜索引擎,负责对商品数据进行索引和检索,选择 Elasticsearch 的原因有以下几点:
Elasticsearch 的插件非常丰富,我给大家介绍其中 waynboot 项目使用的 Elasticsearch 插件。
IK Analyzer 是一个开源的中文分词器,由阿里巴巴集团发布。它采用了细粒度切分和歧义处理等技术,能够较好地处理各种中文文本。IK Analyzer 支持普通模式、搜索模式和拼音模式三种分词方式,并可以根据需要自定义字典。
Pinyin Analyzer 插件是一个用于将中文字符转换为拼音的插件,它集成了 NLP 工具(nlp-lang)。该插件包含了分析器:pinyin,分词器:pinyin 和 token-filter:pinyin。该插件还提供了一些可选的参数,可以控制拼音的输出格式,例如是否保留首字母,是否保留全拼,是否保留非中文字符等。
在 waynboot-mall 项目中,给 Elasticsearch 定义了专门的数据访问层 waynboot-data-elastic,该层目录结构如下:
|-- waynboot-data // 数据访问层 | |-- waynboot-data-elastic // Elasticsearch访问配置模块 | |-- config | |-- constant | |-- mananger
包目录说明如下:
在 waynboot-mall 项目中,Elasticsearch 主要用于支持首页商品的分词搜索、分页排序等功能。Elasticsearch 版本是 7.0,以下实战讲解都是在 7.0 版本基础上进行。
要使用 Elasticsearch ik 分词器进行中文分词搜索,首先需要安装相应的插件 elasticsearch-analysis-ik,然后在创建索引时指定使用中文分词器作为字段的 analyzer 属性。
在日常对 Elasticsearch 的操作中,我们可以通过 rest api 的方式进行操作。
如下我们可以创建一个索引名称为 goods,包含两个属性 title、content。并且 这两个属性都使用 ik 分词器。注意这里我用的 Elasticsearch 提供 Rest api 方式创建索引。
PUT /goods { "settings": { "index": { "number_of_shards": 1, "number_of_replicas": 0 } }, "mappings": { "properties": { "title": { "type": "text", "analyzer": "ik_max_word" }, "content": { "type": "text", "analyzer": "ik_max_word" } } } }
创建索引后,就可以向索引中添加两条数据,例如:
POST /books/_doc/1 { "title": "格林童话", "content": "这本书介绍了很多童话故事,有白雪公主、狮子王、美人鱼等。" } POST /books/_doc/2 { "title": "中国童话故事", "content": "这本书介绍了很多中国童话故事。" }
然后我们就可以使用 match 语法来进行中文分词检索,这里我查询 goods 索引中,title 属性是 "动画" 的记录。如下:
GET /books/_search { "query":{ "match":{ "title": "童话" } } }
查询结果如下:
{ "took": 0, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 2, "relation": "eq" }, "max_score": 0.11190013, "hits": [ { "_index": "books", "_type": "_doc", "_id": "1", "_score": 0.11190013, "_source": { "title": "格林童话", "content": "这本书介绍了很多童话故事,有白雪公主、狮子王、美人鱼等。" } }, { "_index": "books", "_type": "_doc", "_id": "2", "_score": 0.099543065, "_source": { "title": "中国童话故事", "content": "这本书介绍了很多中国童话故事。" } } ] } }
可以看到,查询结果中匹配了标题包含“童话”的文档,这说明 Elasticsearch 使用了中文分词器对查询字符串和文档进行了分词,并根据相关性得分返回了结果。
在 waynboot-mall 项目中,商城首页顶部提供了商品搜索栏,用户可以输入商品名称搜索自己想要的商品,搜索结果展示后,还可以进行热门、新品过滤以及价格、销量等进行排序。
图片
可以看到搜索功能还是比较复杂的,在 waynboot-mall 项目中,这些逻辑全部在 Elasticsearch 内部进行处理,代码如下:
@RestController @AllArgsConstructor @RequestMapping("search") public class SearchController extends BaseController { private IGoodsService iGoodsService; private ElasticDocument elasticDocument; @GetMapping("result") public R result(SearchVO searchVO) throws IOException { // 获取筛选、排序条件 Long memberId = MobileSecurityUtils.getUserId(); String keyword = searchVO.getKeyword(); Boolean filterNew = searchVO.getFilterNew(); Boolean filterHot = searchVO.getFilterHot(); Boolean isNew = searchVO.getIsNew(); Boolean isHot = searchVO.getIsHot(); Boolean isPrice = searchVO.getIsPrice(); Boolean isSales = searchVO.getIsSales(); String orderBy = searchVO.getOrderBy(); SearchHistory searchHistory = new SearchHistory(); if (memberId != null && StringUtils.isNotEmpty(keyword)) { searchHistory.setCreateTime(LocalDateTime.now()); searchHistory.setUserId(memberId); searchHistory.setKeyword(keyword); } Page<SearchVO> page = getPage(); // 查询包含关键字、已上架商品 SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); MatchQueryBuilder matchFiler = QueryBuilders.matchQuery("isOnSale", true); MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("name", keyword); MatchPhraseQueryBuilder matchPhraseQueryBuilder = QueryBuilders.matchPhraseQuery("keyword", keyword); boolQueryBuilder.filter(matchFiler).should(matchQuery).should(matchPhraseQueryBuilder).minimumShouldMatch(1); searchSourceBuilder.timeout(new TimeValue(10, TimeUnit.SECONDS)); // 按是否新品排序 if (isNew) { searchSourceBuilder.sort(new FieldSortBuilder("isNew").order(SortOrder.DESC)); } // 按是否热品排序 if (isHot) { searchSourceBuilder.sort(new FieldSortBuilder("isHot").order(SortOrder.DESC)); } // 按价格高低排序 if (isPrice) { searchSourceBuilder.sort(new FieldSortBuilder("retailPrice").order("asc".equals(orderBy) ? SortOrder.ASC : SortOrder.DESC)); } // 按销量排序 if (isSales) { searchSourceBuilder.sort(new FieldSortBuilder("sales").order(SortOrder.DESC)); } // 筛选新品 if (filterNew) { MatchQueryBuilder filterQuery = QueryBuilders.matchQuery("isNew", true); boolQueryBuilder.filter(filterQuery); } // 筛选热品 if (filterHot) { MatchQueryBuilder filterQuery = QueryBuilders.matchQuery("isHot", true); boolQueryBuilder.filter(filterQuery); } // 组装Elasticsearch查询条件 searchSourceBuilder.query(boolQueryBuilder); // Elasticsearch分页相关 searchSourceBuilder.from((int) (page.getCurrent() - 1) * (int) page.getSize()); searchSourceBuilder.size((int) page.getSize()); // 执行Elasticsearch查询 List<JSONObject> list = elasticDocument.search("goods", searchSourceBuilder, JSONObject.class); List<Integer> goodsIdList = list.stream().map(jsonObject -> (Integer) jsonObject.get("id")).collect(Collectors.toList()); if (goodsIdList.isEmpty()) { return R.success().add("goods", Collections.emptyList()); } // 根据Elasticsearch中返回商品ID查询商品详情并保持es中的排序 List<Goods> goodsList = iGoodsService.searchResult(goodsIdList); Map<Integer, Goods> goodsMap = goodsList.stream().collect(Collectors.toMap(goods -> Math.toIntExact(goods.getId()), o -> o)); List<Goods> returnGoodsList = new ArrayList<>(goodsList.size()); for (Integer goodsId : goodsIdList) { returnGoodsList.add(goodsMap.get(goodsId)); } if (CollectionUtils.isNotEmpty(goodsList)) { AsyncManager.me().execute(new TimerTask() { @Override public void run() { searchHistory.setHasGoods(true); iSearchHistoryService.save(searchHistory); } }); } return R.success().add("goods", returnGoodsList); } }
这里对上面商城的搜索代码给大家做一个讲解:
本文给大家讲解了 waynboot-mall 项目中对于 elasticsearch 的使用以及代码实战讲解。希望能帮助大家更好理解 elasticsearch,大家在自己的项目中如果要引入 elasticsearch,可以直接参照本文的示例代码即可使用。
本文链接:http://www.28at.com/showinfo-26-75299-0.htmlElasticsearch使用实战以及代码详解
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
上一篇: MediatR让进程内通信如此简单,基于MediatR实现事件订阅发布功能
下一篇: Python中容易被忽视的核心功能