ToB企服应用市场:ToB评测及商务社交产业平台

标题: Elasticsearch使用实战以及代码详解 [打印本页]

作者: 涛声依旧在    时间: 2024-5-12 13:47
标题: Elasticsearch使用实战以及代码详解
Elasticsearch 是一个使用 Java 语言编写、遵守 Apache 协议、支持 RESTful 风格的分布式全文搜索和分析引擎,它基于 Lucene 库构建,并提供多种语言的 API。Elasticsearch 可以对任何类型的数据举行索引、查询和聚合分析,无论是文本、数字、地理空间、结构化还黑白结构化的。Elasticsearch 的核心功能是搜索,它可以对数据举行分词匹配、相干性评分、高亮显示等操作,返回相干度高的结果列表。Elasticsearch 也可以用作数据分析,它可以对数据举行统计、分类、聚类等操作,返回聚合结果或图表。
本文将用我开源的 waynboot-mall 项目作于代码讲解,Elasticsearch 版本是 7.10.1。
waynboot-mall 是一套全部开源的微商城项目,包罗三个项目:运营背景、H5 商城和后端接口。实现了一套完整的商城业务,有首页展示、商品分类、商品详情、sku 详情、商品搜索、加入购物车、结算下单、支付宝/微信支付、订单列表、商品评论等一系列功能。
本文大纲如下,

应用场景

Elasticsearch 的典型应用场景有以下几种:
waynboot-mall 商城选择使用 Elasticsearch 作为搜索引擎,负责对商品数据举行索引和检索,选择 Elasticsearch 的原因有以下几点,
waynboot 项目使用的 Elasticsearch 插件

Elasticsearch 的插件非常丰富,我给各人介绍其中 waynboot 项目使用的 Elasticsearch 插件。
IK Analyzer

IK Analyzer 是一个开源的中文分词器,由阿里巴巴集团发布。它采用了细粒度切分和歧义处理等技能,能够较好地处理各种中文文本。IK Analyzer 支持普通模式、搜索模式和拼音模式三种分词方式,并可以根据需要自定义字典。
Pinyin Analyzer

Pinyin Analyzer 插件是一个用于将中文字符转换为拼音的插件,它集成了 NLP 工具(nlp-lang)。该插件包罗了分析器:pinyin,分词器:pinyin 和 token-filter:pinyin。该插件还提供了一些可选的参数,可以控制拼音的输出格式,例如是否保存首字母,是否保存全拼,是否保存非中文字符等。
目录结构

在 waynboot-mall 项目中,给 Elasticsearch 定义了专门的数据访问层 waynboot-data-elastic,该层目录结构如下,
  1.     |-- waynboot-data                    // 数据访问层
  2.     |   |-- waynboot-data-elastic        // Elasticsearch访问配置模块
  3.     |       |-- config
  4.     |       |-- constant
  5.     |       |-- mananger
复制代码
包目录说明如下,
代码实战

在 waynboot-mall 项目中,Elasticsearch 主要用于支持首页商品的分词搜索、分页排序等功能。Elasticsearch 版本是 7.0,以下实战讲解都是在 7.0 版本基础上举行。
要使用 Elasticsearch ik 分词器举行中文分词搜索,首先需要安装相应的插件 elasticsearch-analysis-ik,然后在创建索引时指定使用中文分词器作为字段的 analyzer 属性。
在日常对 Elasticsearch 的操作中,我们可以通过 rest api 的方式举行操作。
Elasticsearch rest api 操作

如下我们可以创建一个索引名称为 goods,包罗两个属性 title、content。并且 这两个属性都使用 ik 分词器。注意这里我用的 Elasticsearch 提供 Rest api 方式创建索引。
  1.     PUT /goods
  2.     {
  3.         "settings": {
  4.             "index": {
  5.                 "number_of_shards": 1,
  6.                 "number_of_replicas": 0
  7.             }
  8.         },
  9.         "mappings": {
  10.             "properties": {
  11.                 "title": {
  12.                     "type": "text",
  13.                     "analyzer": "ik_max_word"
  14.                 },
  15.                 "content": {
  16.                     "type": "text",
  17.                     "analyzer": "ik_max_word"
  18.                 }
  19.             }
  20.         }
  21.     }
复制代码
创建索引后,就可以向索引中添加两条数据,例如:
  1.     POST /books/_doc/1
  2.     {
  3.         "title": "格林童话",
  4.         "content": "这本书介绍了很多童话故事,有白雪公主、狮子王、美人鱼等。"
  5.     }
  6.     POST /books/_doc/2
  7.     {
  8.         "title": "中国童话故事",
  9.         "content": "这本书介绍了很多中国童话故事。"
  10.     }
复制代码
然后我们就可以使用 match 语法来举行中文分词检索,这里我查询 goods 索引中,title 属性是 "动画" 的记录。如下,
  1.     GET /books/_search
  2.     {
  3.         "query":{
  4.             "match":{
  5.                 "title": "童话"
  6.             }
  7.         }
  8.     }
复制代码
查询结果如下,
  1.     {
  2.         "took": 0,
  3.         "timed_out": false,
  4.         "_shards": {
  5.             "total": 1,
  6.             "successful": 1,
  7.             "skipped": 0,
  8.             "failed": 0
  9.         },
  10.         "hits": {
  11.             "total": {
  12.                 "value": 2,
  13.                 "relation": "eq"
  14.             },
  15.             "max_score": 0.11190013,
  16.             "hits": [
  17.                 {
  18.                     "_index": "books",
  19.                     "_type": "_doc",
  20.                     "_id": "1",
  21.                     "_score": 0.11190013,
  22.                     "_source": {
  23.                         "title": "格林童话",
  24.                         "content": "这本书介绍了很多童话故事,有白雪公主、狮子王、美人鱼等。"
  25.                     }
  26.                 },
  27.                 {
  28.                     "_index": "books",
  29.                     "_type": "_doc",
  30.                     "_id": "2",
  31.                     "_score": 0.099543065,
  32.                     "_source": {
  33.                         "title": "中国童话故事",
  34.                         "content": "这本书介绍了很多中国童话故事。"
  35.                     }
  36.                 }
  37.             ]
  38.         }
  39.     }
复制代码
可以看到,查询结果中匹配了标题包罗“童话”的文档,这说明 Elasticsearch 使用了中文分词器对查询字符串和文档举行了分词,并根据相干性得分返回了结果。
全文搜索以及筛选排序

在 waynboot-mall 项目中,商城首页顶部提供了商品搜索栏,用户可以输入商品名称搜索自己想要的商品,搜索结果展示后,还可以举行热门、新品过滤以及价格、销量等举行排序。

可以看到搜索功能还是比力复杂的,在 waynboot-mall 项目中,这些逻辑全部在 Elasticsearch 内部举行处理,代码如下,
  1.     @RestController
  2.     @AllArgsConstructor
  3.     @RequestMapping("search")
  4.     public class SearchController extends BaseController {
  5.         private IGoodsService iGoodsService;
  6.         private ElasticDocument elasticDocument;
  7.         @GetMapping("result")
  8.         public R result(SearchVO searchVO) throws IOException {
  9.             // 获取筛选、排序条件
  10.             Long memberId = MobileSecurityUtils.getUserId();
  11.             String keyword = searchVO.getKeyword();
  12.             Boolean filterNew = searchVO.getFilterNew();
  13.             Boolean filterHot = searchVO.getFilterHot();
  14.             Boolean isNew = searchVO.getIsNew();
  15.             Boolean isHot = searchVO.getIsHot();
  16.             Boolean isPrice = searchVO.getIsPrice();
  17.             Boolean isSales = searchVO.getIsSales();
  18.             String orderBy = searchVO.getOrderBy();
  19.             SearchHistory searchHistory = new SearchHistory();
  20.             if (memberId != null && StringUtils.isNotEmpty(keyword)) {
  21.                 searchHistory.setCreateTime(LocalDateTime.now());
  22.                 searchHistory.setUserId(memberId);
  23.                 searchHistory.setKeyword(keyword);
  24.             }
  25.             Page<SearchVO> page = getPage();
  26.             // 查询包含关键字、已上架商品
  27.             SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  28.             BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
  29.             MatchQueryBuilder matchFiler = QueryBuilders.matchQuery("isOnSale", true);
  30.             MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("name", keyword);
  31.             MatchPhraseQueryBuilder matchPhraseQueryBuilder = QueryBuilders.matchPhraseQuery("keyword", keyword);
  32.             boolQueryBuilder.filter(matchFiler).should(matchQuery).should(matchPhraseQueryBuilder).minimumShouldMatch(1);
  33.             searchSourceBuilder.timeout(new TimeValue(10, TimeUnit.SECONDS));
  34.             // 按是否新品排序
  35.             if (isNew) {
  36.                 searchSourceBuilder.sort(new FieldSortBuilder("isNew").order(SortOrder.DESC));
  37.             }
  38.             // 按是否热品排序
  39.             if (isHot) {
  40.                 searchSourceBuilder.sort(new FieldSortBuilder("isHot").order(SortOrder.DESC));
  41.             }
  42.             // 按价格高低排序
  43.             if (isPrice) {
  44.                 searchSourceBuilder.sort(new FieldSortBuilder("retailPrice").order("asc".equals(orderBy) ? SortOrder.ASC : SortOrder.DESC));
  45.             }
  46.             // 按销量排序
  47.             if (isSales) {
  48.                 searchSourceBuilder.sort(new FieldSortBuilder("sales").order(SortOrder.DESC));
  49.             }
  50.             // 筛选新品
  51.             if (filterNew) {
  52.                 MatchQueryBuilder filterQuery = QueryBuilders.matchQuery("isNew", true);
  53.                 boolQueryBuilder.filter(filterQuery);
  54.             }
  55.             // 筛选热品
  56.             if (filterHot) {
  57.                 MatchQueryBuilder filterQuery = QueryBuilders.matchQuery("isHot", true);
  58.                 boolQueryBuilder.filter(filterQuery);
  59.             }
  60.             // 组装Elasticsearch查询条件
  61.             searchSourceBuilder.query(boolQueryBuilder);
  62.             // Elasticsearch分页相关
  63.             searchSourceBuilder.from((int) (page.getCurrent() - 1) * (int) page.getSize());
  64.             searchSourceBuilder.size((int) page.getSize());
  65.             // 执行Elasticsearch查询
  66.             List<JSONObject> list = elasticDocument.search("goods", searchSourceBuilder, JSONObject.class);
  67.             List<Integer> goodsIdList = list.stream().map(jsonObject -> (Integer) jsonObject.get("id")).collect(Collectors.toList());
  68.             if (goodsIdList.isEmpty()) {
  69.                 return R.success().add("goods", Collections.emptyList());
  70.             }
  71.             // 根据Elasticsearch中返回商品ID查询商品详情并保持es中的排序
  72.             List<Goods> goodsList = iGoodsService.searchResult(goodsIdList);
  73.             Map<Integer, Goods> goodsMap = goodsList.stream().collect(Collectors.toMap(goods -> Math.toIntExact(goods.getId()), o -> o));
  74.             List<Goods> returnGoodsList = new ArrayList<>(goodsList.size());
  75.             for (Integer goodsId : goodsIdList) {
  76.                 returnGoodsList.add(goodsMap.get(goodsId));
  77.             }
  78.             if (CollectionUtils.isNotEmpty(goodsList)) {
  79.                 AsyncManager.me().execute(new TimerTask() {
  80.                     @Override
  81.                     public void run() {
  82.                         searchHistory.setHasGoods(true);
  83.                         iSearchHistoryService.save(searchHistory);
  84.                     }
  85.                 });
  86.             }
  87.             return R.success().add("goods", returnGoodsList);
  88.         }
  89.     }
复制代码
这里对上面商城的搜索代码给各人做一个讲解:
总结一下

本文给各人讲解了 waynboot-mall 项目中对于 elasticsearch 的使用以及代码实战讲解。希望能帮助各人更好理解 elasticsearch,各人在自己的项目中如果要引入 elasticsearch,可以直接参照本文的示例代码即可使用。
想要获取 waynboot-mall 项目源码的同学,可以关注我公众号【步伐员 wayn】,发送 waynboot-mall 即可领取。
如果觉得这篇文章写的不错的话,不妨点赞加关注,我会更新更多技能干货、项目教学、经验分享的文章。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4