摘要

对于一家公司而言,数据量越来越多,如果快速去查找这些信息是一个很难的问题,在计算机领域有一个专门的领域IR(Information Retrival)研究如果获取信息,做信息检索。

在国内的如百度这样的搜索引擎也属于这个领域,要自己实现一个搜索引擎是非常难的,不过信息查找对每一个公司都非常重要,对于开发人员也可以选则一些市场上的开源项目来构建自己的站内搜索引擎,本文将通过ElasticSearch来构建一个这样的信息检索项目。

1 技术选型

  • 搜索引擎服务使用 ElasticSearch

  • 提供的对外 web 服务选则 Springboot web

1.1 ElasticSearch

Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎。Elasticsearch用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。

官方客户端在Java、.NET(C#)、PHP、Python、Apache Groovy、Ruby和许多其他语言中都是可用的。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr,也是基于Lucene。

现在开源的搜索引擎在市面上最常见的就是ElasticSearch和Solr,二者都是基于Lucene的实现,其中ElasticSearch相对更加重量级,在分布式环境表现也更好,二者的选则需考虑具体的业务场景和数据量级。对于数据量不大的情况下,完全需要使用像Lucene这样的搜索引擎服务,通过关系型数据库检索即可。

1.2 Spring Boot

Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”.

现在 Spring Boot 在做 web 开发上是绝对的主流,其不仅仅是开发上的优势,在布署,运维各个方面都有着非常不错的表现,并且 Spring 生态圈的影响力太大了,可以找到各种成熟的解决方案。

1.3 ik分词器

ElasticSearch 本身不支持中文的分词,需要安装中文分词插件,如果需要做中文的信息检索,中文分词是基础,此处选则了ik,下载好后放入 elasticSearch 的安装位置的 plugin 目录即可。

2 环境准备

需要安装好elastiSearch以及kibana(可选),并且需要lk分词插件。

  • 安装elasticSearch elasticsearch官网. 笔者使用的是7.5.1。

  • ik插件下载 ik插件github地址. 注意下载和你下载elasticsearch版本一样的ik插件。

  • 将ik插件放入elasticsearch安装目录下的plugins包下,新建报名ik,将下载好的插件解压到该目录下即可,启动es的时候会自动加载该插件。

搭建 Spring Boot 项目 idea ->new project ->spring initializer

3 项目架构

  • 获取数据使用ik分词插件

  • 将数据存储在es引擎中

  • 通过es检索方式对存储的数据进行检索

  • 使用es的java客户端提供外部服务

4 实现效果

4.1 搜索页面

简单实现一个类似百度的搜索框即可。

4.2 搜索结果页面

点击第一个搜索结果是我个人的某一篇博文,为了避免数据版权问题,笔者在es引擎中存放的全是个人的博客数据。

5 具体代码实现

5.1 全文检索的实现对象

按照博文的基本信息定义了如下实体类,主要需要知道每一个博文的url,通过检索出来的文章具体查看要跳转到该url。

package com.lbh.es.entity;import com.fasterxml.jackson.annotation.JsonIgnore;
import javax.persistence.*;
/*** PUT articles* {* "mappings":* {"properties":{* "author":{"type":"text"},* "content":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart"},* "title":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart"},* "createDate":{"type":"date","format":"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd"},* "url":{"type":"text"}* } },* "settings":{*     "index":{*       "number_of_shards":1,*       "number_of_replicas":2*     }*   }* }* ---------------------------------------------------------------------------------------------------------------------* Copyright(c)lbhbinhao@163.com* @author liubinhao* @date 2021/3/3*/
@Entity
@Table(name = "es_article")
public class ArticleEntity {@Id@JsonIgnore@GeneratedValue(strategy = GenerationType.IDENTITY)private long id;@Column(name = "author")private String author;@Column(name = "content",columnDefinition="TEXT")private String content;@Column(name = "title")private String title;@Column(name = "createDate")private String createDate;@Column(name = "url")private String url;public String getAuthor() {return author;}public void setAuthor(String author) {this.author = author;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}public String getTitle() {return title;}public void setTitle(String title) {this.title = title;}public String getCreateDate() {return createDate;}public void setCreateDate(String createDate) {this.createDate = createDate;}public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}
}

5.2 客户端配置

通过java配置es的客户端。

/*** Copyright(c)lbhbinhao@163.com* @author liubinhao* @date 2021/3/3*/
@Configuration
public class EsConfig {@Value("${elasticsearch.schema}")private String schema;@Value("${elasticsearch.address}")private String address;@Value("${elasticsearch.connectTimeout}")private int connectTimeout;@Value("${elasticsearch.socketTimeout}")private int socketTimeout;@Value("${elasticsearch.connectionRequestTimeout}")private int tryConnTimeout;@Value("${elasticsearch.maxConnectNum}")private int maxConnNum;@Value("${elasticsearch.maxConnectPerRoute}")private int maxConnectPerRoute;@Beanpublic RestHighLevelClient restHighLevelClient() {// 拆分地址List<HttpHost> hostLists = new ArrayList<>();String[] hostList = address.split(",");for (String addr : hostList) {String host = addr.split(":")[0];String port = addr.split(":")[1];hostLists.add(new HttpHost(host, Integer.parseInt(port), schema));}// 转换成 HttpHost 数组HttpHost[] httpHost = hostLists.toArray(new HttpHost[]{});// 构建连接对象RestClientBuilder builder = RestClient.builder(httpHost);// 异步连接延时配置builder.setRequestConfigCallback(requestConfigBuilder -> {requestConfigBuilder.setConnectTimeout(connectTimeout);requestConfigBuilder.setSocketTimeout(socketTimeout);requestConfigBuilder.setConnectionRequestTimeout(tryConnTimeout);return requestConfigBuilder;});// 异步连接数配置builder.setHttpClientConfigCallback(httpClientBuilder -> {httpClientBuilder.setMaxConnTotal(maxConnNum);httpClientBuilder.setMaxConnPerRoute(maxConnectPerRoute);return httpClientBuilder;});return new RestHighLevelClient(builder);}
}

5.3 业务代码编写

包括一些检索文章的信息,可以从文章标题,文章内容以及作者信息这些维度来查看相关信息。

/*** Copyright(c)lbhbinhao@163.com* @author liubinhao* @date 2021/3/3*/
@Service
public class ArticleService {private static final String ARTICLE_INDEX = "article";@Resourceprivate RestHighLevelClient client;@Resourceprivate ArticleRepository articleRepository;public boolean createIndexOfArticle(){Settings settings = Settings.builder().put("index.number_of_shards", 1).put("index.number_of_replicas", 1).build();
// {"properties":{"author":{"type":"text"},
// "content":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart"}
// ,"title":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart"},
// ,"createDate":{"type":"date","format":"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd"}
// }String mapping = "{\"properties\":{\"author\":{\"type\":\"text\"},\n" +"\"content\":{\"type\":\"text\",\"analyzer\":\"ik_max_word\",\"search_analyzer\":\"ik_smart\"}\n" +",\"title\":{\"type\":\"text\",\"analyzer\":\"ik_max_word\",\"search_analyzer\":\"ik_smart\"}\n" +",\"createDate\":{\"type\":\"date\",\"format\":\"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd\"}\n" +"},\"url\":{\"type\":\"text\"}\n" +"}";CreateIndexRequest indexRequest = new CreateIndexRequest(ARTICLE_INDEX).settings(settings).mapping(mapping,XContentType.JSON);CreateIndexResponse response = null;try {response = client.indices().create(indexRequest, RequestOptions.DEFAULT);} catch (IOException e) {e.printStackTrace();}if (response!=null) {System.err.println(response.isAcknowledged() ? "success" : "default");return response.isAcknowledged();} else {return false;}}public boolean deleteArticle(){DeleteIndexRequest request = new DeleteIndexRequest(ARTICLE_INDEX);try {AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);return response.isAcknowledged();} catch (IOException e) {e.printStackTrace();}return false;}public IndexResponse addArticle(ArticleEntity article){Gson gson = new Gson();String s = gson.toJson(article);//创建索引创建对象IndexRequest indexRequest = new IndexRequest(ARTICLE_INDEX);//文档内容indexRequest.source(s,XContentType.JSON);//通过client进行http的请求IndexResponse re = null;try {re = client.index(indexRequest, RequestOptions.DEFAULT);} catch (IOException e) {e.printStackTrace();}return re;}public void transferFromMysql(){articleRepository.findAll().forEach(this::addArticle);}public List<ArticleEntity> queryByKey(String keyword){SearchRequest request = new SearchRequest();/** 创建  搜索内容参数设置对象:SearchSourceBuilder* 相对于matchQuery,multiMatchQuery针对的是多个fi eld,也就是说,当multiMatchQuery中,fieldNames参数只有一个时,其作用与matchQuery相当;* 而当fieldNames有多个参数时,如field1和field2,那查询的结果中,要么field1中包含text,要么field2中包含text。*/SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();searchSourceBuilder.query(QueryBuilders.multiMatchQuery(keyword, "author","content","title"));request.source(searchSourceBuilder);List<ArticleEntity> result = new ArrayList<>();try {SearchResponse search = client.search(request, RequestOptions.DEFAULT);for (SearchHit hit:search.getHits()){Map<String, Object> map = hit.getSourceAsMap();ArticleEntity item = new ArticleEntity();item.setAuthor((String) map.get("author"));item.setContent((String) map.get("content"));item.setTitle((String) map.get("title"));item.setUrl((String) map.get("url"));result.add(item);}return result;} catch (IOException e) {e.printStackTrace();}return null;}public ArticleEntity queryById(String indexId){GetRequest request = new GetRequest(ARTICLE_INDEX, indexId);GetResponse response = null;try {response = client.get(request, RequestOptions.DEFAULT);} catch (IOException e) {e.printStackTrace();}if (response!=null&&response.isExists()){Gson gson = new Gson();return gson.fromJson(response.getSourceAsString(),ArticleEntity.class);}return null;}
}

5.4 对外接口

和使用springboot开发web程序相同。

/*** Copyright(c)lbhbinhao@163.com* @author liubinhao* @date 2021/3/3*/
@RestController
@RequestMapping("article")
public class ArticleController {@Resourceprivate ArticleService articleService;@GetMapping("/create")public boolean create(){return articleService.createIndexOfArticle();}@GetMapping("/delete")public boolean delete() {return articleService.deleteArticle();}@PostMapping("/add")public IndexResponse add(@RequestBody ArticleEntity article){return articleService.addArticle(article);}@GetMapping("/fransfer")public String transfer(){articleService.transferFromMysql();return "successful";}@GetMapping("/query")public List<ArticleEntity> query(String keyword){return articleService.queryByKey(keyword);}
}

5.5 页面

此处页面使用thymeleaf,主要原因是笔者真滴不会前端,只懂一丢丢简单的h5,就随便做了一个可以展示的页面。

搜索页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>YiyiDu</title><!--input:focus设定当输入框被点击时,出现蓝色外边框text-indent: 11px;和padding-left: 11px;设定输入的字符的起始位置与左边框的距离--><style>input:focus {border: 2px solid rgb(62, 88, 206);}input {text-indent: 11px;padding-left: 11px;font-size: 16px;}
</style><!--input初始状态--><style class="input/css">.input {width: 33%;height: 45px;vertical-align: top;box-sizing: border-box;border: 2px solid rgb(207, 205, 205);border-right: 2px solid rgb(62, 88, 206);border-bottom-left-radius: 10px;border-top-left-radius: 10px;outline: none;margin: 0;display: inline-block;background: url(/static/img/camera.jpg) no-repeat 0 0;background-position: 565px 7px;background-size: 28px;padding-right: 49px;padding-top: 10px;padding-bottom: 10px;line-height: 16px;}
</style><!--button初始状态--><style class="button/css">.button {height: 45px;width: 130px;vertical-align: middle;text-indent: -8px;padding-left: -8px;background-color: rgb(62, 88, 206);color: white;font-size: 18px;outline: none;border: none;border-bottom-right-radius: 10px;border-top-right-radius: 10px;margin: 0;padding: 0;}
</style>
</head>
<body>
<!--包含table的div-->
<!--包含input和button的div--><div style="font-size: 0px;"><div align="center" style="margin-top: 0px;"><img src="../static/img/yyd.png" th:src = "@{/static/img/yyd.png}"  alt="一亿度" width="280px" class="pic" /></div><div align="center"><!--action实现跳转--><form action="/home/query"><input type="text" class="input" name="keyword" /><input type="submit" class="button" value="一亿度下" /></form></div></div>
</body>
</html>

搜索结果页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/4.3.1/css/bootstrap.min.css"><meta charset="UTF-8"><title>xx-manager</title>
</head>
<body>
<header th:replace="search.html"></header>
<div class="container my-2"><ul th:each="article : ${articles}"><a th:href="${article.url}"><li th:text="${article.author}+${article.content}"></li></a></ul>
</div>
<footer th:replace="footer.html"></footer>
</body>
</html>

往期推荐

2.3W字,这可能是把Nginx讲得最全面的一篇文章了,建议收藏备用

架构设计分享:项目用得到的多级缓存架构设计方案

案例分享,git项目持续集成实践

SpringBoot + Redis搭建支撑10w 人的秒杀抢单系统!

100多个高频MySQL面试问题及答案

Java8 Stream:20+实际例子,玩转集合的筛选、归约、分组、聚合

扩展Redis的JSON处理模块,非常强调性能的RedisJson!速学

ELK 搭建 TB 级海量日志监控系统,这个太强了!

回复干货】获取精选干货视频教程

回复加群】加入疑难问题攻坚交流群

回复mat】获取内存溢出问题分析详细文档教程

回复赚钱】获取用java写一个能赚钱的微信机器人

回复副业】获取程序员副业攻略一份

好文请点赞+分享

基于Elasticsearch 实现站内全文搜索相关推荐

  1. 基于 Es 实现站内全文搜索

    点击上方关注 "终端研发部" 设为"星标",和你一起掌握更多数据库知识 摘要 对于一家公司而言,数据量越来越多,如果快速去查找这些信息是一个很难的问题,在计算机 ...

  2. python elasticsearch 入门教程(二) ---全文搜索

    python elasticsearch 入门教程(二) ---全文搜索 截止目前的搜索相对都很简单:单个姓名,通过年龄过滤.现在尝试下稍微高级点儿的全文搜索--一项 传统数据库确实很难搞定的任务. ...

  3. [转]测试淘宝站内的搜索系统

    如果让你来测试淘宝站内的搜索系统,请问你能想到哪些方法来测试? 参考:http://bbs.51testing.com/thread-499381-1-1.html 1.功能方面,是否能按指定条件查到 ...

  4. 测试淘宝站内的搜索系统

    如果让你来测试淘宝站内的搜索系统,请问你能想到哪些方法来测试? 参考:http://bbs.51testing.com/thread-499381-1-1.html 1.功能方面,是否能按指定条件查到 ...

  5. Elasticsearch用例:全文搜索

    在本系列有关Elasticsearch用例的最后一篇文章中,我们介绍了Elasticsearch提供的用于存储甚至大量文档的功能 . 在这篇文章中,我们将研究其另一个核心功能:搜索. 我正在利用上一篇 ...

  6. 【项目】 基于BOOST的站内搜索引擎

    目录 1. 简介 建立搜索引擎的宏观体系 技术栈和项目环境 正排索引 and 倒排索引 2. 数据去标签与数据清洗模块 -- Parser 数据去标签 parser.cc parser.cc 的代码结 ...

  7. 【elasticsearch】elasticsearch 精确匹配 与 全文搜索

    本文为博主九师兄(QQ:541711153 欢迎来探讨技术)原创文章,未经允许博主不允许转载. 可以加我问问题,免费解答,有问题可以先私聊我,本人每天都在线,会帮助需要的人. 但是本博主因为某些原因, ...

  8. 加入一个基于GOOGLE的站内搜索引擎

    由于这一次的客户只能提供虚拟主机作为项目运行平台,无法搭配中文分词组件,原来自行开发的站内搜索引擎无法发挥最大的功效(主要是不能自动分析关键词,只能通过指定相关索引字段,以及手工输入TAG的机制来生成 ...

  9. 关闭WordPress站内容搜,杜绝违禁词在站内恶意搜索

    废话不多说,先上图,这应该算是利用WordPress的站内搜索进行恶意攻击,不仅会导致搜索引擎爬取或可能得收录之外,还会导致降权,在此先鄙视这波傻逼一下!上代码,直接关闭站内搜索. 可能有人会质疑了, ...

  10. 软件测试-测试淘宝站内的搜索系统测试用例

    测试思路:功能,性能,易用性,兼容性,容错性几方面进行测试 1.功能方面,是否能按指定条件查到正确.完整的结果,具体: 1.1录入条件为可查到结果的正常关键字.词.语句,检索到的内容.链接正确性: 1 ...

最新文章

  1. jenkins邮件配置
  2. 13Flyweight(享元)模式
  3. node.js+express,实现RESTful API
  4. 【Android】 Android中Log调试详解
  5. WebSocket教程
  6. Python flask使用实例
  7. Ionic4.x 中自定义公共模块
  8. Python:Python全球生态主站,pip安装方法、集成安装方法、文件安装方法、第三方库自动安装脚本
  9. Java多线程学习总结(6)——深入理解悲观锁与乐观锁
  10. 是==和!=相互依赖?
  11. 微软打造Unity开发工具包 为视力低落的用户增加辅助功能
  12. jmeter---ftp性能测试
  13. html 获取ie浏览器,用C#从,IE浏览器中获取HTML文档
  14. 计算机大类专业分流问题,2019级计算机大类专业分流实施细则
  15. 亿级视频内容如何实时更新?
  16. 资料:成为全栈dApp开发者的学习计划
  17. python英语词汇读音_利用PYTHON 爬虫爬出自己的英语单词库
  18. cv.bitwise_and用法
  19. 病毒木马查杀实战第004篇:熊猫烧香之专杀工具的编写
  20. 什么是ISO(国际标准化组织)?

热门文章

  1. 我个人总结的Halcon内存管理心得笔记,关于C#/C++内存释放
  2. go开发报 A required privilege is not held by the client 错误
  3. Win11新建不了文本文档?Win11右键无法新建文本文档的解决方法
  4. hp proliant dl380从U盘启动按哪个键
  5. java编写时间流逝_JAVA日期时间类如何使用
  6. 一键禁用windows defender
  7. RGBA(0,0,0,0)调色
  8. torch.nn.Embedding(num_embeddings, embedding_dim)的理解
  9. 移动端怎么让图片不失真_图片怎样放大后不模糊 图片放大不失真的方法步骤...
  10. C1认证之计算机通识知识及习题总结——我的学习笔记