翼度科技»论坛 编程开发 .net 查看内容

我的网站集成ElasticSearch初体验

4

主题

4

帖子

12

积分

新手上路

Rank: 1

积分
12
   最近,我给我的网站(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里面,我开启了密码认证。下面是我的配置
  1. "Search": {
  2.     "IsEnable": "true",
  3.     "Uri": "http://127.0.0.1:9200/",
  4.     "User": "123",
  5.     "Password": "123"
  6.   }
  7. 然后新增一个程序集
复制代码
然后再ElasticsearchClient里面去写一个构造函数去配置es
  1. using Core.Common;
  2. using Core.CPlatform;
  3. using Core.SearchEngine.Attr;
  4. using Elastic.Clients.Elasticsearch;
  5. using Elastic.Clients.Elasticsearch.IndexManagement;
  6. using Elastic.Transport;
  7. namespace Core.SearchEngine.Client
  8. {
  9.     public class ElasticSearchClient : IElasticSearchClient
  10.     {
  11.         private ElasticsearchClient elasticsearchClient;
  12.         public ElasticSearchClient()
  13.         {
  14.             string uri = ConfigureProvider.configuration.GetSection("Search:Uri").Value;
  15.             string username = ConfigureProvider.configuration.GetSection("Search:User").Value;
  16.             string password = ConfigureProvider.configuration.GetSection("Search:Password").Value;
  17.             var settings = new ElasticsearchClientSettings(new Uri(uri))
  18.                           .Authentication(new BasicAuthentication(username, password)).DisableDirectStreaming();
  19.             elasticsearchClient = new ElasticsearchClient(settings);
  20.         }
  21.         public ElasticsearchClient GetClient()
  22.         {
  23.             return elasticsearchClient;
  24.         }
  25.     }
  26. }
复制代码
   然后,我们看skd的官网有这个这个提示

 客户端应用程序应创建一个 该实例,该实例在整个应用程序中用于整个应用程序 辈子。在内部,客户端管理和维护与节点的 HTTP 连接, 重复使用它们以优化性能。如果您使用依赖项注入 容器中,客户端实例应注册到 单例生存期
所以我直接给它来一个AddSingleton
  1. using Core.SearchEngine.Client;
  2. using Microsoft.Extensions.DependencyInjection;
  3. namespace Core.SearchEngine
  4. {
  5.     public static class ConfigureSearchEngine
  6.     {
  7.         public static void AddSearchEngine(this IServiceCollection services)
  8.         {
  9.             services.AddSingleton<IElasticSearchClient, ElasticSearchClient>();
  10.         }
  11.     }
  12. }
复制代码
2、提交文章并且同步到es
 然后就是同步文章到es了,我是先写入数据库,再同步到rabbitmq,通过事件总线(基于事件总线EventBus实现邮件推送功能)写入到es
先定义一个es模型
  1. using Core.SearchEngine.Attr;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. using XianDan.Model.BizEnum;
  8. namespace XianDan.Domain.Article
  9. {
  10.     [ElasticsearchIndex(IndexName ="t_article")]//自定义的特性,sdk并不包含这个特性
  11.     public class Article_ES
  12.     {
  13.         public long Id { get; set; }
  14.         /// <summary>
  15.         /// 作者
  16.         /// </summary>
  17.         public string Author { get; set; }
  18.         /// <summary>
  19.         /// 标题                                                                              
  20.         /// </summary>
  21.         public string Title { get; set; }
  22.         /// <summary>
  23.         /// 标签
  24.         /// </summary>
  25.         public string Tag { get; set; }
  26.         /// <summary>
  27.         /// 简介                                                                              
  28.         /// </summary>
  29.         public string Description { get; set; }
  30.         /// <summary>
  31.         /// 内容
  32.         /// </summary>
  33.         public string ArticleContent { get; set; }
  34.         /// <summary>
  35.         /// 专栏
  36.         /// </summary>
  37.         public long ArticleCategoryId { get; set; }
  38.         /// <summary>
  39.         /// 是否原创
  40.         /// </summary>
  41.         public bool? IsOriginal { get; set; }
  42.         /// <summary>
  43.         /// 评论数
  44.         /// </summary>
  45.         public int? CommentCount { get; set; }
  46.         /// <summary>
  47.         /// 点赞数
  48.         /// </summary>
  49.         public int? PraiseCount { get; set; }
  50.         /// <summary>
  51.         /// 浏览次数
  52.         /// </summary>
  53.         public int? BrowserCount { get; set; }
  54.         /// <summary>
  55.         /// 收藏数量
  56.         /// </summary>
  57.         public int? CollectCount { get; set; }
  58.         /// <summary>
  59.         /// 创建时间
  60.         /// </summary>
  61.         public DateTime CreateTime { get; set; }
  62.     }
  63. }
复制代码
然后创建索引
  1. string index = esArticleClient.GetIndexName(typeof(Article_ES));
  2.             await esArticleClient.GetClient().Indices.CreateAsync<Article_ES>(index, s =>
  3.             s.Mappings(
  4.                 x => x.Properties(
  5.                     t => t.LongNumber(l => l.Id)
  6.                          .Text(l=>l.Title,z=>z.Analyzer(ik_max_word))
  7.                          .Keyword(l=>l.Author)
  8.                          .Text(l=>l.Tag,z=>z.Analyzer(ik_max_word))
  9.                          .Text(l=>l.Description,z=>z.Analyzer(ik_max_word))
  10.                          .Text(l=>l.ArticleContent,z=>z.Analyzer(ik_max_word))
  11.                          .LongNumber(l=>l.ArticleCategoryId)
  12.                          .Boolean(l=>l.IsOriginal)
  13.                          .IntegerNumber(l=>l.BrowserCount)
  14.                          .IntegerNumber(l=>l.PraiseCount)
  15.                          .IntegerNumber(l=>l.PraiseCount)
  16.                          .IntegerNumber(l=>l.CollectCount)
  17.                          .IntegerNumber(l=>l.CommentCount)
  18.                          .Date(l=>l.CreateTime)
  19.                     )
  20.                 )
  21.             );
复制代码
然后每次增删改文章的时候写入到mq,例如
  1. private async Task SendToMq(Article article, Operation operation)
  2.         {
  3.             ArticleEventData articleEventData = new ArticleEventData();
  4.             articleEventData.Operation = operation;
  5.             articleEventData.Article_ES = MapperUtil.Map<Article, Article_ES>(article);
  6.             TaskRecord taskRecord = new TaskRecord();
  7.             taskRecord.Id = CreateEntityId();
  8.             taskRecord.TaskType = TaskRecordType.MQ;
  9.             taskRecord.TaskName = "发送文章";
  10.             taskRecord.TaskStartTime = DateTime.Now;
  11.             taskRecord.TaskStatu = (int)MqMessageStatu.New;
  12.             articleEventData.Unique = taskRecord.Id.ToString();
  13.             taskRecord.TaskValue = JsonConvert.SerializeObject(articleEventData);
  14.             await unitOfWork.GetRepository<TaskRecord>().InsertAsync(taskRecord);
  15.             await unitOfWork.CommitAsync();
  16.             try
  17.             {
  18.                 eventBus.Publish(GetMqExchangeName(), ExchangeType.Direct, BizKey.ArticleQueueName, articleEventData);
  19.             }
  20.             catch (Exception ex)
  21.             {
  22.                 var taskRecordRepository = unitOfWork.GetRepository<TaskRecord>();
  23.                 TaskRecord update = await taskRecordRepository.SelectByIdAsync(taskRecord.Id);
  24.                 update.TaskStatu = (int)MqMessageStatu.Fail;
  25.                 update.LastUpdateTime = DateTime.Now;
  26.                 update.TaskResult = "发送失败";
  27.                 update.AdditionalData = ex.Message;
  28.                 await taskRecordRepository.UpdateAsync(update);
  29.                 await unitOfWork.CommitAsync();
  30.             }
  31.         }
复制代码
mq订阅之后写入es,具体的增删改的方法就不写了吧
3、开始查询es
  等待写入文章之后,开始查询文章,这里sdk提供的查询的方法比较复杂,全都是通过lmbda一个个链式去拼接的,但是我又没有找到更好的方法,所以就先这样吧
   先创建一个集合存放查询的表达式
  1. List<Action<QueryDescriptor<Article_ES>>> querys = new List<Action<QueryDescriptor<Article_ES>>>();
复制代码
   然后定义一个几个需要查询的字段
   我这里使用MultiMatch来实现多个字段匹配同一个查询条件,并且指定使用ik_smart分词
  1. Field[] fields =
  2.                 {
  3.                     new Field("title"),
  4.                     new Field("tag"),
  5.                     new Field("articleContent"),
  6.                     new Field("description")
  7.                 };
  8. 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;}
  1. Dictionary<Field, HighlightField> highlightFields = new Dictionary<Field, HighlightField>();
  2.             highlightFields.Add(new Field("title"), new HighlightField()
  3.             {
  4.                 PreTags = new List<string> { "<em>" },
  5.                 PostTags = new List<string> { "</em>" },
  6.             });
  7.             highlightFields.Add(new Field("description"), new HighlightField()
  8.             {
  9.                 PreTags = new List<string> { "<em>" },
  10.                 PostTags = new List<string> { "</em>" },
  11.             });
  12.             Highlight highlight = new Highlight()
  13.             {
  14.                 Fields = highlightFields
  15.             };
复制代码
为了提高查询的效率,我只查部分的字段
  1. SourceFilter sourceFilter = new SourceFilter();
  2.             sourceFilter.Includes = Fields.FromFields(new Field[] { "title", "id", "author", "description", "createTime", "browserCount", "commentCount" });
  3.             SourceConfig sourceConfig = new SourceConfig(sourceFilter);
  4.             Action<SearchRequestDescriptor<Article_ES>> configureRequest = s => s.Index(index)
  5.             .From((homeArticleCondition.CurrentPage - 1) * homeArticleCondition.PageSize)
  6.             .Size(homeArticleCondition.PageSize)
  7.             .Query(x => x.Bool(y => y.Must(querys.ToArray())))
  8.             .Source(sourceConfig)
  9.              .Sort(y => y.Field(ht => ht.CreateTime, new FieldSort() { Order=SortOrder.Desc}))
复制代码
获取查询的分词结果
  1. var analyzeIndexRequest = new AnalyzeIndexRequest
  2.             {
  3.                 Text = new string[] { keyword },
  4.                 Analyzer = analyzer
  5.             };
  6.             var analyzeResponse = await elasticsearchClient.Indices.AnalyzeAsync(analyzeIndexRequest);
  7.             if (analyzeResponse.Tokens == null)
  8.                 return new string[0];
  9.             return analyzeResponse.Tokens.Select(s => s.Token).ToArray();
复制代码
到此,这个就是大致的查询结果,完整的如下
  1. public async Task SelectArticle(HomeArticleCondition homeArticleCondition)        {            string keyword = homeArticleCondition.Keyword.Trim();            bool isNumber = Regex.IsMatch(keyword, RegexPattern.IsNumberPattern);            List querys = new List();            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>();
  2.             highlightFields.Add(new Field("title"), new HighlightField()
  3.             {
  4.                 PreTags = new List<string> { "<em>" },
  5.                 PostTags = new List<string> { "</em>" },
  6.             });
  7.             highlightFields.Add(new Field("description"), new HighlightField()
  8.             {
  9.                 PreTags = new List<string> { "<em>" },
  10.                 PostTags = new List<string> { "</em>" },
  11.             });
  12.             Highlight highlight = new Highlight()
  13.             {
  14.                 Fields = highlightFields
  15.             };            SourceFilter sourceFilter = new SourceFilter();
  16.             sourceFilter.Includes = Fields.FromFields(new Field[] { "title", "id", "author", "description", "createTime", "browserCount", "commentCount" });
  17.             SourceConfig sourceConfig = new SourceConfig(sourceFilter);
  18.             Action<SearchRequestDescriptor<Article_ES>> configureRequest = s => s.Index(index)
  19.             .From((homeArticleCondition.CurrentPage - 1) * homeArticleCondition.PageSize)
  20.             .Size(homeArticleCondition.PageSize)
  21.             .Query(x => x.Bool(y => y.Must(querys.ToArray())))
  22.             .Source(sourceConfig)
  23.              .Sort(y => y.Field(ht => ht.CreateTime, new FieldSort() { Order=SortOrder.Desc})).Highlight(highlight);            var resp = await esArticleClient.GetClient().SearchAsync(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 articles = resp.Documents.ToList();            return new Core.SearchEngine.Response.SearchResponse(articles, analyzeWords);        }
复制代码
4、演示效果    
搞完之后,发布部署,看看效果,分词这里要想做的像百度那样,估计目前来看非常有难度的

   那么这里我也向大家求教一下,如何使用SearchRequest封装多个查询条件,如下
SearchRequest searchRequest = new SearchRequest();
 searchRequest.From = 0;
searchRequest.Size = 10;
  searchRequest.Query=多个查询条件
因为我觉得这样代码读起来比lambda可读性高些,能更好的动态封装。
  1.  
复制代码
来源:https://www.cnblogs.com/MrHanBlog/p/18425152
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具