我的网站集成ElasticSearch初体验

news/2024/11/15 16:00:17/文章来源:https://www.cnblogs.com/MrHanBlog/p/18425152

   最近,我给我的网站(https://www.xiandanplay.com/)尝试集成了一下es来实现我的一个搜索功能,因为这个是我第一次了解运用elastic,所以如果有不对的地方,大家可以指出来,话不多说,先看看我的一个大致流程

      这里我采用的sdk的版本是Elastic.Clients.Elasticsearch, Version=8.0.0.0,官方的网址Installation | Elasticsearch .NET Client [8.0] | Elastic

      我的es最开始打算和我的应用程序一起部署到ubuntu上面,结果最后安装kibana的时候,各种问题,虽好无奈,只好和我的SqlServer一起安装到windows上面,对于一个2G内容的服务器来说,属实有点遭罪了。

1、配置es

 在es里面,我开启了密码认证。下面是我的配置

"Search": {"IsEnable": "true","Uri": "http://127.0.0.1:9200/","User": "123","Password": "123"}
然后新增一个程序集

然后再ElasticsearchClient里面去写一个构造函数去配置es

using Core.Common;
using Core.CPlatform;
using Core.SearchEngine.Attr;
using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.IndexManagement;
using Elastic.Transport;namespace Core.SearchEngine.Client
{public class ElasticSearchClient : IElasticSearchClient{private ElasticsearchClient elasticsearchClient;public ElasticSearchClient(){string uri = ConfigureProvider.configuration.GetSection("Search:Uri").Value;string username = ConfigureProvider.configuration.GetSection("Search:User").Value;string password = ConfigureProvider.configuration.GetSection("Search:Password").Value;var settings = new ElasticsearchClientSettings(new Uri(uri)).Authentication(new BasicAuthentication(username, password)).DisableDirectStreaming();elasticsearchClient = new ElasticsearchClient(settings);}public ElasticsearchClient GetClient(){return elasticsearchClient;}}
}

   然后,我们看skd的官网有这个这个提示

 客户端应用程序应创建一个 该实例,该实例在整个应用程序中用于整个应用程序 辈子。在内部,客户端管理和维护与节点的 HTTP 连接, 重复使用它们以优化性能。如果您使用依赖项注入 容器中,客户端实例应注册到 单例生存期

所以我直接给它来一个AddSingleton

using Core.SearchEngine.Client;
using Microsoft.Extensions.DependencyInjection;namespace Core.SearchEngine
{public static class ConfigureSearchEngine{public static void AddSearchEngine(this IServiceCollection services){services.AddSingleton<IElasticSearchClient, ElasticSearchClient>();}}
}

2、提交文章并且同步到es

 然后就是同步文章到es了,我是先写入数据库,再同步到rabbitmq,通过事件总线(基于事件总线EventBus实现邮件推送功能)写入到es

先定义一个es模型

using Core.SearchEngine.Attr;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using XianDan.Model.BizEnum;namespace XianDan.Domain.Article
{[ElasticsearchIndex(IndexName ="t_article")]//自定义的特性,sdk并不包含这个特性public class Article_ES{public long Id { get; set; }/// <summary>/// 作者/// </summary>public string Author { get; set; }/// <summary>/// 标题                                                                               /// </summary>public string Title { get; set; }/// <summary>/// 标签/// </summary>public string Tag { get; set; }/// <summary>/// 简介                                                                              /// </summary>public string Description { get; set; }/// <summary>/// 内容/// </summary>public string ArticleContent { get; set; }/// <summary>/// 专栏/// </summary>public long ArticleCategoryId { get; set; }/// <summary>/// 是否原创/// </summary>public bool? IsOriginal { get; set; }/// <summary>/// 评论数/// </summary>public int? CommentCount { get; set; }/// <summary>/// 点赞数/// </summary>public int? PraiseCount { get; set; }/// <summary>/// 浏览次数/// </summary>public int? BrowserCount { get; set; }/// <summary>/// 收藏数量/// </summary>public int? CollectCount { get; set; }/// <summary>/// 创建时间/// </summary>public DateTime CreateTime { get; set; }}
}

然后创建索引

 string index = esArticleClient.GetIndexName(typeof(Article_ES));await esArticleClient.GetClient().Indices.CreateAsync<Article_ES>(index, s =>s.Mappings(x => x.Properties(t => t.LongNumber(l => l.Id).Text(l=>l.Title,z=>z.Analyzer(ik_max_word)).Keyword(l=>l.Author).Text(l=>l.Tag,z=>z.Analyzer(ik_max_word)).Text(l=>l.Description,z=>z.Analyzer(ik_max_word)).Text(l=>l.ArticleContent,z=>z.Analyzer(ik_max_word)).LongNumber(l=>l.ArticleCategoryId).Boolean(l=>l.IsOriginal).IntegerNumber(l=>l.BrowserCount).IntegerNumber(l=>l.PraiseCount).IntegerNumber(l=>l.PraiseCount).IntegerNumber(l=>l.CollectCount).IntegerNumber(l=>l.CommentCount).Date(l=>l.CreateTime))));

然后每次增删改文章的时候写入到mq,例如

 private async Task SendToMq(Article article, Operation operation){ArticleEventData articleEventData = new ArticleEventData();articleEventData.Operation = operation;articleEventData.Article_ES = MapperUtil.Map<Article, Article_ES>(article);TaskRecord taskRecord = new TaskRecord();taskRecord.Id = CreateEntityId();taskRecord.TaskType = TaskRecordType.MQ;taskRecord.TaskName = "发送文章";taskRecord.TaskStartTime = DateTime.Now;taskRecord.TaskStatu = (int)MqMessageStatu.New;articleEventData.Unique = taskRecord.Id.ToString();taskRecord.TaskValue = JsonConvert.SerializeObject(articleEventData);await unitOfWork.GetRepository<TaskRecord>().InsertAsync(taskRecord);await unitOfWork.CommitAsync();try{eventBus.Publish(GetMqExchangeName(), ExchangeType.Direct, BizKey.ArticleQueueName, articleEventData);}catch (Exception ex){var taskRecordRepository = unitOfWork.GetRepository<TaskRecord>();TaskRecord update = await taskRecordRepository.SelectByIdAsync(taskRecord.Id);update.TaskStatu = (int)MqMessageStatu.Fail;update.LastUpdateTime = DateTime.Now;update.TaskResult = "发送失败";update.AdditionalData = ex.Message;await taskRecordRepository.UpdateAsync(update);await unitOfWork.CommitAsync();}}

mq订阅之后写入es,具体的增删改的方法就不写了吧

3、开始查询es

  等待写入文章之后,开始查询文章,这里sdk提供的查询的方法比较复杂,全都是通过lmbda一个个链式去拼接的,但是我又没有找到更好的方法,所以就先这样吧

   先创建一个集合存放查询的表达式

List<Action<QueryDescriptor<Article_ES>>> querys = new List<Action<QueryDescriptor<Article_ES>>>();

   然后定义一个几个需要查询的字段

   我这里使用MultiMatch来实现多个字段匹配同一个查询条件,并且指定使用ik_smart分词

Field[] fields ={new Field("title"),new Field("tag"),new Field("articleContent"),new Field("description")};querys.Add(s => s.MultiMatch(y => y.Fields(Fields.FromFields(fields)).Analyzer(ik_smart).Query(keyword).Type(TextQueryType.MostFields)));

定义查询结果高亮,给查询出来的匹配到的分词的字段添加标签,同时前端需要对这个样式处理,

:deep(.search-words) em {
    color: #ee0f29;
    font-style: initial;
}
 Dictionary<Field, HighlightField> highlightFields = new Dictionary<Field, HighlightField>();highlightFields.Add(new Field("title"), new HighlightField(){PreTags = new List<string> { "<em>" },PostTags = new List<string> { "</em>" },});highlightFields.Add(new Field("description"), new HighlightField(){PreTags = new List<string> { "<em>" },PostTags = new List<string> { "</em>" },});Highlight highlight = new Highlight(){Fields = highlightFields};

为了提高查询的效率,我只查部分的字段

 SourceFilter sourceFilter = new SourceFilter();sourceFilter.Includes = Fields.FromFields(new Field[] { "title", "id", "author", "description", "createTime", "browserCount", "commentCount" });SourceConfig sourceConfig = new SourceConfig(sourceFilter);Action<SearchRequestDescriptor<Article_ES>> configureRequest = s => s.Index(index).From((homeArticleCondition.CurrentPage - 1) * homeArticleCondition.PageSize).Size(homeArticleCondition.PageSize).Query(x => x.Bool(y => y.Must(querys.ToArray()))).Source(sourceConfig).Sort(y => y.Field(ht => ht.CreateTime, new FieldSort() { Order=SortOrder.Desc}))

获取查询的分词结果

 var analyzeIndexRequest = new AnalyzeIndexRequest{Text = new string[] { keyword },Analyzer = analyzer};var analyzeResponse = await elasticsearchClient.Indices.AnalyzeAsync(analyzeIndexRequest);if (analyzeResponse.Tokens == null)return new string[0];return analyzeResponse.Tokens.Select(s => s.Token).ToArray();

到此,这个就是大致的查询结果,完整的如下

 public async Task<Core.SearchEngine.Response.SearchResponse<Article_ES>> SelectArticle(HomeArticleCondition homeArticleCondition){string keyword = homeArticleCondition.Keyword.Trim();bool isNumber = Regex.IsMatch(keyword, RegexPattern.IsNumberPattern);List<Action<QueryDescriptor<Article_ES>>> querys = new List<Action<QueryDescriptor<Article_ES>>>();if (isNumber){querys.Add(s => s.Bool(x => x.Should(should => should.Term(f => f.Field(z => z.Title).Value(keyword)), should => should.Term(f => f.Field(z => z.Tag).Value(keyword)), should => should.Term(f => f.Field(z => z.ArticleContent).Value(keyword)))));}else{Field[] fields ={new Field("title"),new Field("tag"),new Field("articleContent"),new Field("description")};querys.Add(s => s.MultiMatch(y => y.Fields(Fields.FromFields(fields)).Analyzer(ik_smart).Query(keyword).Type(TextQueryType.MostFields)));}if (homeArticleCondition.ArticleCategoryId.HasValue){querys.Add(s => s.Term(t => t.Field(f => f.ArticleCategoryId).Value(FieldValue.Long(homeArticleCondition.ArticleCategoryId.Value))));}string index = esArticleClient.GetIndexName(typeof(Article_ES));Dictionary<Field, HighlightField> highlightFields = new Dictionary<Field, HighlightField>();highlightFields.Add(new Field("title"), new HighlightField(){PreTags = new List<string> { "<em>" },PostTags = new List<string> { "</em>" },});highlightFields.Add(new Field("description"), new HighlightField(){PreTags = new List<string> { "<em>" },PostTags = new List<string> { "</em>" },});Highlight highlight = new Highlight(){Fields = highlightFields};SourceFilter sourceFilter = new SourceFilter();sourceFilter.Includes = Fields.FromFields(new Field[] { "title", "id", "author", "description", "createTime", "browserCount", "commentCount" });SourceConfig sourceConfig = new SourceConfig(sourceFilter);Action<SearchRequestDescriptor<Article_ES>> configureRequest = s => s.Index(index).From((homeArticleCondition.CurrentPage - 1) * homeArticleCondition.PageSize).Size(homeArticleCondition.PageSize).Query(x => x.Bool(y => y.Must(querys.ToArray()))).Source(sourceConfig).Sort(y => y.Field(ht => ht.CreateTime, new FieldSort() { Order=SortOrder.Desc})).Highlight(highlight);var resp = await esArticleClient.GetClient().SearchAsync<Article_ES>(configureRequest);foreach (var item in resp.Hits){if (item.Highlight == null)continue;foreach (var dict in item.Highlight){switch (dict.Key){case "title":item.Source.Title = string.Join("...", dict.Value);break;case "description":item.Source.Description = string.Join("...", dict.Value);break;}}}string[] analyzeWords = await esArticleClient.AnalyzeAsync(homeArticleCondition.Keyword);List<Article_ES> articles = resp.Documents.ToList();return new Core.SearchEngine.Response.SearchResponse<Article_ES>(articles, analyzeWords);}

4、演示效果    

搞完之后,发布部署,看看效果,分词这里要想做的像百度那样,估计目前来看非常有难度的

   那么这里我也向大家求教一下,如何使用SearchRequest封装多个查询条件,如下

SearchRequest searchRequest = new SearchRequest();
 searchRequest.From = 0;
searchRequest.Size = 10;
  searchRequest.Query=多个查询条件

因为我觉得这样代码读起来比lambda可读性高些,能更好的动态封装。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/801452.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Flipper Zero极客的便携式多功能工具设备

官网:Flipper Zero — 极客的便携式多功能工具设备 Flipper Zero是近两年比较热门的硬件工具,官方固件主要涵盖的功能为Sub-Ghz,125kHz,NFC,红外。 基本信息资料都可以在官方网站找到比较详细的文档解释。本篇主要是一个基础入门,这系列也是给自己学习此硬件一个上手研究…

C盘扩容免费工具

1.diskgenius 下载 https://www.diskgenius.cn/download.php 解压即可使用,无需安装 2.下载 安装Windows_PE环境 https://www.diskgenius.cn/help/windows_aik_adk_installnotes.php?Version=0A000000&Build=22631&Lang=936 官方软件,安全五毒 3.运行diskgenius ,点…

Leetcode 2464. 有效分割中的最少子数组数目

1.题目基本信息 1.1.题目描述 给定一个整数数组 nums。 如果要将整数数组 nums 拆分为 子数组 后是 有效的,则必须满足: 每个子数组的第一个和最后一个元素的最大公约数 大于 1,且 nums 的每个元素只属于一个子数组。 返回 nums 的 有效 子数组拆分中的 最少 子数组数目。如果…

debian 系统服务器搭建

在VMWare中安装Debian12.5虚拟机后, 需要开始进行一些设置:1. 先设置网络模式为桥接模式, 这样和主机在同一个局域网,方便后续ssh连接 2. 第1步设置后,重启Debian,登录后, 查看IP和Mac地址, 192.168.31.16,00:0c:29:6c:31:e6 3. 设置路由器,固定IP: 192.168.31.105。 …

初识算法

持续更新数据结构与算法专题,欢迎关注.......1.1 什么是算法? 定义 在数学和计算机科学领域,算法是一系列有限的严谨指令,通常用于解决一类特定问题或执行计算In mathematics and computer science, an algorithm (/ˈlɡərɪəm/) is a finite sequence of rigorous inst…

解锁Java线程池:实战技巧与陷阱规避

线程池作为初学者常感困惑的一个领域,本次“巧手打字通课堂”将深入剖析其中几个最为普遍的误区。专业在线打字练习网站-巧手打字通,只输出有价值的知识。一 前言 线程池作为初学者常感困惑的一个领域,本次“巧手打字通课堂”将深入剖析其中几个最为普遍的误区。为了更清晰地…

FFmpeg开发笔记(五十四)使用EasyPusher实现移动端的RTSP直播

​之前的文章《利用RTMP协议构建电脑与手机的直播Demo》介绍了如何使用RTMP Streamer实现完整的RTMP直播流程,另一篇文章《利用SRT协议构建手机APP的直播Demo》介绍了如何使用SRT Streamer实现完整的SRT直播流程,接下来介绍如何使用EasyPusher-Android实现完整的RTSP直播流程…

小飞机配置

小飞机配置把两个文件的文件复制到小飞机的安装路径中小飞机路径:打开文件所在的位置将MSI Afterburner中的文件复制找到RTSS安装路径RTSS设置中文小飞机监控设置选择自己需要显示内容进行OSD显示

Spring原理基础

Spring 高级 1 容器与Bean 1.1 接口容器 1.1.1 BeanFactory是什么 @SpringBootApplication public class ShowApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(ShowApplication.class, args);/*** 1、…

springboot+vite 商品管理

SpringBoot + Vue3 +MySql5.7 +JDK8 一、 SpringBoot项目搭建 1 SpringBoot概述 1.1 SpringBoot 概念 SpringBoot提供了一种快速使用Spring的方式,基于约定优于配置的思想,可以让开发人员不必在配置与逻 辑业务之间进行思维的切换,全身心的投入到逻辑业务的代码编写中,从而…

SaaS业务架构:业务能力分析

大家好,我是汤师爷~ 今天聊聊SaaS业务架构的业务能力分析。 业务能力概述 简单来说,业务能力是企业“做某事的能力”。 业务能力描述了企业当前和未来应对挑战的能力,即企业能做什么或需要做什么。业务能力建模的关键在于定义了企业做什么,而不是如何做(由业务流程描述)。…

Redis常见使用场景

Redis常见使用场景