文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Elasticsearch使用实战以及代码详解

2024-11-30 01:41

关注

Elasticsearch 的核心功能是搜索,它可以对数据进行分词匹配、相关性评分、高亮显示等操作,返回相关度高的结果列表。Elasticsearch 也可以用作数据分析,它可以对数据进行统计、分类、聚类等操作,返回聚合结果或图表。

本文将用我开源的 waynboot-mall 项目作于代码讲解,Elasticsearch 版本是 7.10.1。

waynboot-mall 是一套全部开源的微商城项目,包含三个项目:运营后台、H5 商城和后端接口。实现了一套完整的商城业务,有首页展示、商品分类、商品详情、sku 详情、商品搜索、加入购物车、结算下单、支付宝/微信支付、订单列表、商品评论等一系列功能。

本文大纲如下,

图片

应用场景

Elasticsearch 的典型应用场景有以下几种:

waynboot-mall 商城选择使用 Elasticsearch 作为搜索引擎,负责对商品数据进行索引和检索,选择 Elasticsearch 的原因有以下几点:

  1. Elasticsearch 是一个开源的分布式搜索引擎,基于 Lucene 开发,支持全文检索、结构化检索、地理位置检索等多种类型的检索,功能丰富。
  2. Elasticsearch 本身具有高性能和高可用性的设计,可以通过集群和分片机制实现水平扩展,支持海量数据的存储和处理,适合大规模的商城搜索场景。
  3. Elasticsearch 网上社区活跃,现有互联网上有大量的使用文档和案例,方便入门使用和问题排查。
  4. 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,该层目录结构如下:

|-- 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 的方式进行操作。

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 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 list = elasticDocument.search("goods", searchSourceBuilder, JSONObject.class);
            List 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 goodsList = iGoodsService.searchResult(goodsIdList);
            Map goodsMap = goodsList.stream().collect(Collectors.toMap(goods -> Math.toIntExact(goods.getId()), o -> o));
            List 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,可以直接参照本文的示例代码即可使用。

来源:程序员wayn内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯