最近,我给我的网站(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可读性高些,能更好的动态封装。

 

我的网站集成ElasticSearch初体验的更多相关文章

  1. 【docker Elasticsearch】Rest风格的分布式开源搜索和分析引擎Elasticsearch初体验

    概述: Elasticsearch 是一个分布式.可扩展.实时的搜索与数据分析引擎. 它能从项目一开始就赋予你的数据以搜索.分析和探索的能力,这是通常没有预料到的. 它存在还因为原始数据如果只是躺在磁 ...

  2. 【ES】ElasticSearch初体验之使用Java进行最基本的增删改查~

    好久没写博文了, 最近项目中使用到了ElaticSearch相关的一些内容, 刚好自己也来做个总结. 现在自己也只能算得上入门, 总结下自己在工作中使用Java操作ES的一些小经验吧. 本文总共分为三 ...

  3. ElasticSearch初体验之使用

    好久没写博文了, 最近项目中使用到了ElaticSearch相关的一些内容, 刚好自己也来做个总结.现在自己也只能算得上入门, 总结下自己在工作中使用Java操作ES的一些小经验吧. 本文总共分为三个 ...

  4. Spring boot集成redis初体验

    pom.xml: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="ht ...

  5. Spring boot集成Rabbit MQ使用初体验

    Spring boot集成Rabbit MQ使用初体验 1.rabbit mq基本特性 首先介绍一下rabbitMQ的几个特性 Asynchronous Messaging Supports mult ...

  6. webpack初体验_集成插件_集成loader

    webpack初体验 如果没装 webpack 就先装一下,命令行输入npm i webpack -g 新建一个项目 创建一个空的项目 定义一个名称 创建一个Module 选择静态 web 输入名称 ...

  7. JAVA中使用最广泛的本地缓存?Ehcache的自信从何而来2 —— Ehcache的各种项目集成与使用初体验

    大家好,又见面了. 本文是笔者作为掘金技术社区签约作者的身份输出的缓存专栏系列内容,将会通过系列专题,讲清楚缓存的方方面面.如果感兴趣,欢迎关注以获取后续更新. 在上一篇文章<JAVA中使用最广 ...

  8. 在同一个硬盘上安装多个 Linux 发行版及 Fedora 21 、Fedora 22 初体验

    在同一个硬盘上安装多个 Linux 发行版 以前对多个 Linux 发行版的折腾主要是在虚拟机上完成.我的桌面电脑性能比较强大,玩玩虚拟机没啥问题,但是笔记本电脑就不行了.要在我的笔记本电脑上折腾多个 ...

  9. Question2Answer初体验

    Question2Answer初体验   高质量的问答社区十分有价值,很多无法解决的问题能通过问答社区找到解决办法,而对于站长来说,垂直的问答社区也很有潜力.最近盯上问答这一块,发现和我的一些思路很符 ...

  10. Net Core平台灵活简单的日志记录框架NLog+Mysql组合初体验

    Net Core平台灵活简单的日志记录框架NLog初体验 前几天分享的"[Net Core集成Exceptionless分布式日志功能以及全局异常过滤][https://www.cnblog ...

随机推荐

  1. Java8 Stream流使用

    Java8 Stream 流式编程 一.Lambda表达式 Lambda表达式也可以称为闭包,它是推动Java8发布的最重要新特性,lambda允许把函数作为一个方法参数传递给方法. 在Java8之前 ...

  2. BI 工具如何助力市政设计公司实现数字化转型?

    一.前言 近年来,国家出台多个政策文件来鼓励和发展数字化和智能化,如<十四五规划>提出要推进产业数字化转型.<交通强国建设纲要>提出要大力发展智慧交通.上海市发布的<关于 ...

  3. signal-slot:python版本的多进程通信的信号与槽机制(编程模式)的库(library) —— 强化学习ppo算法库sample-factory的多进程包装器,实现类似Qt的多进程编程模式(信号与槽机制) —— python3.12版本下成功通过测试

    什么是 Qt signal-slot库项目地址: https://github.com/alex-petrenko/signal-slot 该库实现的主要原理: 要注意这个项目的library只是对原 ...

  4. 【转载】 Ubuntu 中开机自动执行脚本的两种方法

    原文地址: https://www.jianshu.com/p/6366d7070642 作者:貘鸣来源:简书 ============================================ ...

  5. 如何修复ubuntu的uefi启动——如何将Ubuntu安装入移动硬盘中

    交代一下使用场景,个人平时经常使用Ubuntu系统,由于不喜欢总在一个地方呆但是来回搬电脑又不是十分的方便,于是想到了一个好的方案,那就是把Ubuntu系统安装到移动硬盘中,这样不论是在家还是在实验室 ...

  6. js 实现俄罗斯方块(三)

    我又来啦!上一篇有点水,本篇我们来干货! 嘿嘿,首先我们先搭建游戏世界------网格 所有的操作包括左移右移下移旋转都是在这个网格中 既然是使用js来写当然跑不了html啦,实现网格最简单的 方法就 ...

  7. pyc文件添加magic头

    pyc文件添加magic头 hexedit插件安装可以去看另一篇文章:http://t.csdnimg.cn/VhqEh 我们用notepad++打开pyc文件,选择插件--->hex-edit ...

  8. RabbitMQ普通集群同一宿主机docker搭建

    1.准备3个节点安装rabbitmq,搭建集群(注意:每个节点启动间隔15~20s,如果启动失败,需删除宿主机文件重新启动) 2.宿主机文件目录手动创建,并设置可操作权限 准备宿主机文件目录 cd / ...

  9. Dijkstra单源最短路模板

    struct DIJ { using i64 = long long; using PII = pair<i64, i64>; vector<i64> dis; vector& ...

  10. A. Flipping Game

    A. Flipping Game 本质上是让我们找出一段区间内\(0\)的个数大于\(1\)的个数的最多的区间,且必须进行一次操作,所以可以考虑区间\(dp\),或者最小子序列和 1 最小子序列和 \ ...