为什么推荐你用Elasticsearch做搜索系统?

知新闻 2023-07-05 17:29:38
102阅读

检索是一个十分普遍的作用,大伙儿毫无疑问都应用过,比如:网页搜索、Google 检索、电子商务商品查询、美团商家/食品类检索这些。

照片来源于 Pexels

伴随着互联网信息可燃性地飞快提高,网友必须更合理的人性化站内搜索。因此互联网技术运用基本上沒有不开发设计检索作用的,即然这一作用那么关键,作为一名达标的程序猿务必弄清楚其身后的完成基本原理。分配!

文中将根据 Spring Boot Elasticsearch Vue 完成一个简易版的电子商务检索系统软件,便捷大伙儿了解身后的基本原理。

经典案例

依据图中能够获知,检索的领域模型实际上蛮简易的,客户键入要检索的产品关键字递交之后,将关键词搜索、页数信息内容等传往后面台,后台管理查看后将結果集开展解决并回到。

必须关心的只不过以下几个方面:

  • 查看速率要快
  • 数据信息匹配度要高
  • 便捷排列
  • 回到結果高亮度

MySQL

关键表(goods)以下:

MySQL 产品表的字段名是十分多的,而商城系统检索网页页面所必须的数据信息只不过便是产品ID、产品名称、产品价格、产品评价数和产品初始图等(依据自身具体开发设计状况而定,这儿依据文中实例前面款式开展剖析)。

一般产品表还会继续选择商品关键字字段名专业便捷百度搜索引擎应用,因此一条单表 SQL 查看坚信应当打不倒诸位。

大家先不考虑到数据库查询特性层面的难题,就单纯性为了更好地完成这一作用讨论一下都必须干什么:

  • 假定检索标准是华为荣耀手机平板,规定是只需达到了在其中随意一个词句组成的数据信息都需要查看出去,SQL 要怎么写?也就是说你准备写是多少条 SQL?
  • 假定上一步你做完了,取得那么多的結果集是不是你要考虑到去重复排列一下?
  • 前二步你都完成了,如今我的规定是回到前面时务必将刚刚检索标准中的词语组成开展高亮度解决

WTF,收到那样的要求如果让你用关联型数据库查询去完成,确实有点儿自讨没趣。该怎么办?往下看。

Elasticsearch

伴随着互联网信息可燃性地飞快提高,传统式的如何查询已没法为网友出示合理的站内搜索。

如果我们做的仅仅用户数量非常少的内部网新项目,而且检索的字段名全是一些內容很简洁明了的字段名,例如名字,序号这类的,那彻底可以用数据库查询 like 句子。

可是,数据库查询 like 查看特性极低,假如检索的要求太多,或是必须检索的是大文字种类的內容(全文检索),那麼这类检索的计划方案也是不可取的。

互联网技术的迅猛发展急切地要求一种迅速、全方位、精确且平稳靠谱的信息内容如何查询。

即然我们要做特性高的全文检索,这一要求又不可以依靠数据库查询,只有由我们自己来完成了。

可是令大家很受严厉打击的是全文检索是难以完成的,大家不但期待能全文检索,还期待它充足平稳充足快,且百度搜索有关键词高亮度,还能按各种各样配对成绩来排列,期待它能转换不一样的匹配算法来达到各种各样词性标注要求。

总的来说,如果我们想要做一个功能齐全,特性强劲的全文检索实际上并非易事,而全文检索也是一个普遍的要求,在这类自然环境下,目前市面上发生了一些开源系统的解决方法。

这种解决方法开源系统出去后,得到了很多的小区开发人员适用,持续为其开发设计软件,使其持续提升和健全,这就变成大家所讲的百度搜索引擎了。而他们中最有名的便是 Elasticsearch 和 Solr。

  • Elasticsearch 是一个根据 Apache Lucene 的检索网络服务器。它出示了一个分布式系统多客户工作能力的全篇百度搜索引擎,根据 RESTful web 插口。
  • Elasticsearch 是用 Java 开发设计的,并做为 Apache 批准条文下的开源论坛公布,是当今时兴的公司级百度搜索引擎。设计方案用以云计算技术中,可以做到即时检索,平稳,靠谱,迅速,安裝方便使用。
  • Elasticsearch 是最火爆的公司百度搜索引擎,次之是 Apache Solr,也是根据 Lucene。
  • 假如应用 Elasticsearch 来做这件事情,刚刚的难题都是会得到解决:
  • Elasticsearch 便于安裝和配备,学习培训和应用成本费较低,拆箱即用。
  • Elasticsearch 适用单机版也适用分布式系统,内嵌分布式系统融洽管理方法作用,与生俱来群集。
  • Elasticsearch 出示了分块和团本体制,一个数据库索引能够分为好几个分块,一个分块能够设定好几个拷贝分块,提高工作效率和高可用性。
  • Elasticsearch 重视于关键作用完成,高級作用多有第三方软件出示,比如图形界面页面 Kibana 的支撑点。
  • Elasticsearch 创建数据库索引快,实用性查看快,适用新起的即时检索运用,应对海量信息也不遑多让,速度更快,负荷强。
  • Elasticseach 有强劲的文本分析作用(词性标注)和全文索引体制,进一步提高检索速率。

①词性标注

刚刚大家提及的难题中,假定检索标准是华为荣耀手机平板,规定是只需达到了在其中随意一个词句组成的数据信息都需要查看出去。

依靠 Elasticseach 的文本分析作用能够轻轻松松将检索标准开展词性标注解决,再融合全文索引完成迅速查找。

Elasticseach 出示了三种词性标注方式:

  • 一个字词性标注
  • 二分法词性标注
  • 词典词性标注

一个字词性标注:

  • 如:“华为荣耀手机平板”
  • 实际效果:“华”、“为”、“手”、“机”、“平”、“板”、“电”、“脑”

二分法词性标注:

  • 按两字开展分割。
  • 如:“华为荣耀手机平板”
  • 实际效果:“华为公司”、“为手”、“手机上”、“机平”、“平板电脑”、“板电”、“电脑上”。

词典词性标注:按某类优化算法结构词,随后去配对已建好的词库结合,假如配对到就分割出去变成词句。

一般词典词性标注被觉得是最理想化的汉语匹配算法。而词典词性标注最常见的便是 IK 词性标注。

IK 分词器出示二种分词模式:

  • ik_max_word:会将文字做最粗粒度的分拆,例如会将“我国国际歌”拆分成“我国,中国人民,中华民族,中国人,人民共和国,老百姓,人,民,中华人民共和国,共和,和,国国,国际歌”,会可循各种各样很有可能的组成,合适 Term Query。
  • ik_smart:会将文字做最细粒度的分拆,例如会将“我国国际歌”拆分成“我国,国际歌”,合适 Phrase Query。

②全文索引

针对百度搜索引擎而言:

  • 正排数据库索引是文本文档 ID 到文本文档內容、英语单词的关联方交易。换句话说根据 ID 获得文本文档的內容。
  • 全文索引是英语单词到文本文档 ID 的关联方交易。换句话说根据单词搜索到文本文档 ID。

全文索引的查看步骤是:最先依据关键词搜索到相匹配的文本文档 ID,随后依据正排数据库索引查看文本文档 ID 的详细內容,最终回到给客户要想的結果。

③构成部分

全文索引是百度搜索引擎的关键,关键包括2个一部分:

  • 英语单词字典(Trem Dictionary):纪录全部的文本文档词性标注后的結果。一般选用 B Tree 的方法,来确保高效率。
  • 倒排序表(Posting List):纪录英语单词相匹配的文本文档的结合,由全文索引项(Posting)构成。

全文索引项(Posting)关键包括以下的信息内容:

  • 文本文档 ID,用以获得初始文本文档的信息内容。
  • 英语单词頻率(TF,Term Frequency),纪录该英语单词在该文本文档中发生的频次,用以事后关联性计分。
  • 部位(Position),纪录英语单词在文本文档中的词性标注部位(好几个),用以做词句检索。
  • 偏位(Offset),纪录英语单词在文本文档的开始与结束部位,用以高亮显示。

④实例

全文索引论文参考文献:《这就是搜索引擎:核心技术详解》张俊林著。

假定文本文档结合包括五个文本文档,每一个文本文档內容如下图所显示。在图上最左方一栏是每一个文本文档相匹配的文本文档序号。大家的每日任务便是对这一文本文档结合创建全文索引。

最先用分词算法将文本文档全自动切分为英语单词编码序列。为了更好地系统软件事后解决便捷,必须对每一个不一样的英语单词授予唯一的英语单词序号,另外纪录什么文本文档中包括这一英语单词,在这般解决完毕后,我们可以获得非常简单的全文索引,如下图所显示。

在图上,“英语单词 ID”一栏纪录了每一个英语单词的英语单词序号,第二栏是相匹配的英语单词,第三栏即每一个英语单词相匹配的倒排序表。例如英语单词 Google,其英语单词序号为 1,倒排序表为 1,2,3,4,5,表明文本文档结合中每一个文本文档都包括了这一英语单词。

为了更好地便捷大伙儿了解,图中仅仅一个简易的全文索引,只纪录了什么文本文档包括什么英语单词。而实际上,数据库索引系统软件还能够纪录此外的其他信息。

下面的图则是一个相对性繁杂的全文索引,在英语单词相匹配的倒排序表格中不但纪录了文本文档序号,还纪录了英语单词頻率信息内容(TF),即这一英语单词在某一文本文档中发生的频次。

往往要纪录这一信息内容,是由于高频词信息内容在百度搜索排列时,测算查看和文本文档相似性是很重要的一个测算因素,因此将其纪录在倒排序表格中,以便捷事后排列时开展得分测算。

图上英语单词 创办人 的英语单词序号为 7,相匹配的倒排序表內容为 (3;1),在其中 3 意味着文本文档序号为 3 的文本文档包括这一英语单词,数据 1 意味着高频词信息内容,即这一英语单词在 3 号文本文档中只发生过 1 次。

好用的全文索引还能够纪录大量的信息内容,如下图所显示数据库索引系统软件除开纪录文本文档序号和英语单词頻率信息内容外,附加记述了两大类信息内容,即每一个英语单词相匹配的“文本文档頻率信息内容”及其在倒排序表格中纪录英语单词在某一文本文档发生的位置信息(POS)。

拥有这一数据库索引系统软件,百度搜索引擎能够很便捷地回应客户的查看,例如客户键入查看词“Facebook”,检索系统软件搜索全文索引,从这当中载入包括这一英语单词的文本文档,这种文本文档便是出示给客户的百度搜索。

运用英语单词頻率信息内容、文本文档頻率信息内容能够对这种备选百度搜索开展排列,测算文本文档和查看的相似度,依照相似度的评分由高到低排列輸出。

总的来说,你懂得的,废话不多说,下边进到实战演练阶段。

准备工作

自然环境:

  • Elasticsearch:7.9.3
  • Spring Boot:2.4.3
  • JDK:11.0.7
  • 前面:仿京东模版 Vue,原文中装有详尽编码
  • IDE:IntelliJ IDEA

Elasticsearch

安裝步骤这儿但是多过多阐释,文中应用 Elasticsearch 群集自然环境,已安裝好 IK 汉语分词器。

下面的图为 elasticsearch-head 软件(电脑浏览器能够立即安裝该软件开展联接)表明的群集信息内容。

Spring Boot

①建立新项目

应用 Spring Initializr 复位 Spring Boot 新项目,加上 Spring Web,Spring Data Elasticsearch,MySQL,MyBatis,Lombok。

②环境变量

application.yml 配备 MySQL、Elasticsearch、MyBatis 基本信息。

 
  1. spring: 
  2.   # 数据库 
  3.   datasource: 
  4.     driver-class-name: com.mysql.cj.jdbc.Driver 
  5.     url: jdbc:mysql://localhost:3306/example?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false 
  6.     username: root 
  7.     password: 123456 
  8.   # elasticsearch 
  9.   elasticsearch: 
  10.     rest: 
  11.       uris: 192.168.10.10:9200,192.168.10.11:9200,192.168.10.12:9200 
  12.  
  13. mybatis: 
  14.   configuration: 
  15.     map-underscore-to-camel-casetrue # 打开骆驼峰投射 

③运行类

运行类加上 Mapper 插口扫描仪。

 
  1. package com.example; 
  2.  
  3. import org.mybatis.spring.annotation.MapperScan; 
  4. import org.springframework.boot.SpringApplication; 
  5. import org.springframework.boot.autoconfigure.SpringBootApplication; 
  6.  
  7. @MapperScan("com.example.mapper") // Mapper 插口扫描仪 
  8. @SpringBootApplication 
  9. public class SearchDemoApplication { 
  10.  
  11.     public static void main(String[] args) { 
  12.         SpringApplication.run(SearchDemoApplication.class, args); 
  13.     } 
  14.  

前面

将我替大伙儿准备好的前端资源文档加上至新项目 resources 文件目录下的 static 文件目录中。

在 list.html 中应用 CDN 加上 Vue 和 Axios 免除下载文件的全过程。

 
  1. <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.min.js"></script> 
  2. <script src="https://cdn.jsdelivr.net/npm/axios@0.21.1/dist/axios.min.js"></script> 

运行

浏览:http://localhost:8080/list.html,实际效果以下:

作用开发设计

创建索引

①要求表明

该作用关键用以将 MySQL 产品信息导进 Elasticseach。

假如贵公司构建了 ELK 系统软件,能够应用 Logstash 将 MySQL 产品信息导进 Elasticseach。这些內容中后期因为我会升级,敬请关注,今日我们不聊这些。

即然沒有 Logstash 那大家就根据编码将 MySQL 数据信息导进 Elasticseach。

MySQL 产品表的字段名是十分多的,而商城系统检索网页页面所必须的数据信息只不过便是产品ID、产品名称、产品价格、产品评价数和产品初始图等(依据自身具体开发设计状况而定,这儿依据文中实例前面款式开展剖析)。

一般产品表还会继续选择商品关键字字段名专业便捷百度搜索引擎应用,因此一条单表 SQL 查看坚信应当打不倒诸位。

②dao层

建立dao层 Goods.java,加上 @Document 注释用以投射 Elasticsearch 数据库索引库:

  • indexName:数据库索引库名字
  • shards:分块数
  • replicas:团本数
  • createIndex:是不是创建索引库
  • @Id:主键
  • @Field:设定数据库索引标准

设定数据库索引标准:

  • type = FieldType.Text:文字,默认设置词性标注
  • analyzer = "ik_max_word":特定词性标注标准,ik_max_word 会将文字最粗粒度开展分拆
  • type = FieldType.Double:双精度浮点型,并做为一个总体不能分
  • type = FieldType.Short:短整形,并做为一个总体不能分
  • type = FieldType.Keyword:文字,并做为一个总体不能分
 
  1. package com.example.pojo; 
  2.  
  3. import lombok.Data; 
  4. import lombok.NoArgsConstructor; 
  5. import org.springframework.data.annotation.Id; 
  6. import org.springframework.data.elasticsearch.annotations.Document; 
  7. import org.springframework.data.elasticsearch.annotations.Field; 
  8. import org.springframework.data.elasticsearch.annotations.FieldType; 
  9.  
  10. import java.io.Serializable
  11. import java.math.BigDecimal; 
  12.  
  13. @Data 
  14. @NoArgsConstructor 
  15. /** 
  16.  * @Document 投射 Elasticsearch 数据库索引库所必须的注释 
  17.  *  indexName:数据库索引库名字 
  18.  *  shards:分块数 
  19.  *  replicas:团本数 
  20.  *  createIndex:是不是创建索引库 
  21.  */ 
  22. @Document(indexName = "goods", shards = 5, replicas = 1, createIndex = true
  23. public class Goods implements Serializable { 
  24.  
  25.     private static final long serialVersionUID = -1989082640160920658L; 
  26.     @Id 
  27.     private Integer goodsId; // 产品ID 
  28.     @Field(type = FieldType.Text, analyzer = "ik_max_word") // 词性标注 
  29.     private String goodsName; // 产品名称 
  30.     @Field(type = FieldType.Text, analyzer = "ik_max_word") // 词性标注 
  31.     private String keywords; // 产品关键字 
  32.     @Field(type = FieldType.Double
  33.     private BigDecimal marketPrice; // 市价 
  34.     @Field(type = FieldType.Short) 
  35.     private Short commentCount; // 产品评价数 
  36.     @Field(type = FieldType.Keyword) 
  37.     private String originalImg; // 产品初始图详细地址 
  38.     private String goodsRemark; // 产品简易叙述 
  39.     private String goodsContent; // 产品详细说明,储存商品详情图详细地址 
  40.     private Integer catId; // 归类ID 
  41.     private Integer extendCatId; // 拓展归类ID 
  42.     private String goodsNo; // 商品编号 
  43.     private Integer clickCount; // 点击量 
  44.     private Short brandId; // 知名品牌ID 
  45.     private Short storeCount; // 库存量总数 
  46.     private Integer weight; // 产品净重,企业:克 
  47.     private BigDecimal shopPrice; // 店铺价 
  48.     private BigDecimal costPrice; // 产品出厂价 
  49.     private Byte isReal; // 是不是为商品 
  50.     private Byte isOnSale; // 是不是发布 
  51.     private Byte isFreeShipping; // 是不是免邮 0 否, 1 是 
  52.     private Integer onTime; // 产品发布時间 
  53.     private Short sort; // 产品排列 
  54.     private Byte isRecommend; // 是不是强烈推荐 
  55.     private Byte isNew; // 是不是新产品 
  56.     private Byte isHot; // 是不是畅销 
  57.     private Integer lastUpdate; // 最终更新 
  58.     private Short goodsType; // 产品隶属种类ID 
  59.     private Short specType; // 商品规格种类 
  60.     private Integer giveIntegral; // 购买商品赠予積分 
  61.     private Integer exchangeIntegral; // 积分换购:0 不参加积分换购 
  62.     private Short suppliersId; // 供应商ID 
  63.     private Integer salesSum; // 产品销售量 
  64.     private Byte promType; // 0 一般订单信息,1 限时秒杀, 2 团购价, 3 营销特惠 
  65.     private Integer promId; // 优惠促销ID 
  66.     private BigDecimal commission; // 提成用以分销商分为 
  67.     private String spu; // SPU 
  68.     private String sku; // SKU 
  69.  

③GoodsMapper.java

从 MySQL 查看 goods 产品表:产品ID、产品名称、产品关键字、市价、产品评价数、产品初始图详细地址。这种字段名用以百度搜索引擎应用。

 
  1. package com.example.mapper; 
  2.  
  3. import com.example.pojo.Goods; 
  4. import org.apache.ibatis.annotations.Select
  5.  
  6. import java.util.List; 
  7.  
  8. /** 
  9.  * @author hello沃德老先生 
  10.  * @微信公众平台 hello沃德老先生 
  11.  * @website https://mrhelloworld.com 
  12.  * @wechat 124059770 
  13.  */ 
  14. public interface GoodsMapper { 
  15.  
  16.     /** 
  17.      * 查看产品ID、产品名称、产品关键字、市价、产品评价数、产品初始图详细地址 
  18.      * 
  19.      * @return 产品列表 
  20.      */ 
  21.     @Select("select goods_id, goods_name, keywords, market_price, comment_count, original_img from goods"
  22.     List<Goods> selectGoodsList(); 
  23.  

④GoodsRepository.java

application.yml 环境变量加上一个自定配备,用以操纵创建索引和设定投射的电源开关。

 
  1. # 自定配备 
  2. search: 
  3.   index
  4.     enabled: true # 是不是必须创建索引和设定投射。默认设置为 false 

GoodsRepository.java 引入 ElasticsearchRestTemplate,撰写创建索引和设定投射有关编码。

 
  1. package com.example.repository; 
  2.  
  3. import com.example.pojo.Goods; 
  4. import org.springframework.beans.factory.annotation.Value; 
  5. import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; 
  6. import org.springframework.data.elasticsearch.core.IndexOperations; 
  7. import org.springframework.data.elasticsearch.core.document.Document; 
  8. import org.springframework.stereotype.Repository; 
  9.  
  10. import javax.annotation.Resource; 
  11. import java.util.Arrays; 
  12. import java.util.List; 
  13.  
  14. /** 
  15.  * @author hello沃德老先生 
  16.  * @微信公众平台 hello沃德老先生 
  17.  * @website https://mrhelloworld.com 
  18.  * @wechat 124059770 
  19.  */ 
  20. @Repository 
  21. public class GoodsRepository { 
  22.  
  23.     @Resource 
  24.     private ElasticsearchRestTemplate elasticsearchRestTemplate; 
  25.  
  26.     // 是不是必须创建索引和设定投射。默认设置为 false 
  27.     @Value("${search.index.enabled}"
  28.     private boolean indexEnabled; 
  29.  
  30.     /** 
  31.      * 大批量增加产品信息至 Elasticsearch 
  32.      * 
  33.      * @param goodsList 
  34.      */ 
  35.     public void save(List<Goods> goodsList) { 
  36.         // 是不是必须创建索引和设定投射 
  37.         if (indexEnabled) { 
  38.             createIndexAndPutMapping(); 
  39.         } 
  40.         // 大批量增加 
  41.         elasticsearchRestTemplate.save(goodsList); 
  42.     } 
  43.  
  44.     /** 
  45.      * 增加单独产品信息至 Elasticsearch 
  46.      * 
  47.      * @param goods 
  48.      */ 
  49.     public void save(Goods goods) { 
  50.         save(Arrays.asList(goods)); 
  51.     } 
  52.  
  53.     /** 
  54.      * 创建索引和设定投射 
  55.      */ 
  56.     private void createIndexAndPutMapping() { 
  57.         // 设定数据库索引信息内容(dao层) 
  58.         IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(Goods.class); 
  59.         // 创建索引 
  60.         indexOperations.create(); 
  61.         // 建立投射 
  62.         Document mapping = indexOperations.createMapping(); 
  63.         // 将投射载入数据库索引 
  64.         indexOperations.putMapping(mapping); 
  65.     } 
  66.  

⑤SearchService.java

SearchService.java 引入 GoodsMapper 和 GoodsRepository,完成将 MySQL 产品信息导进 Elasticsearch。

 
  1. package com.example.service; 
  2.  
  3. import com.example.mapper.GoodsMapper; 
  4. import com.example.repository.GoodsRepository; 
  5. import org.springframework.stereotype.Service; 
  6.  
  7. import javax.annotation.Resource; 
  8.  
  9. /** 
  10.  * @author hello沃德老先生 
  11.  * @微信公众平台 hello沃德老先生 
  12.  * @website https://mrhelloworld.com 
  13.  * @wechat 124059770 
  14.  */ 
  15. @Service 
  16. public class SearchService { 
  17.  
  18.     @Resource 
  19.     private GoodsMapper goodsMapper; 
  20.     @Resource 
  21.     private GoodsRepository goodsRepository; 
  22.  
  23.     /** 
  24.      * 将 MySQL 产品信息导进 Elasticsearch 
  25.      */ 
  26.     public void importGoods() { 
  27.         goodsRepository.save(goodsMapper.selectGoodsList()); 
  28.     } 
  29.  

⑥单元测试卷

针对当今实例来讲,这些作用没必要曝露一个插口去启用,立即內部撰写单元测试卷进行就可以。

 
  1. package com.example.service; 
  2.  
  3. import com.example.SearchDemoApplication; 
  4. import org.junit.jupiter.api.Test; 
  5. import org.springframework.boot.test.context.SpringBootTest; 
  6.  
  7. import javax.annotation.Resource; 
  8.  
  9. @SpringBootTest(classes = SearchDemoApplication.class) 
  10. public class SearchServiceTest { 
  11.  
  12.     @Resource 
  13.     private SearchService searchService; 
  14.  
  15.     @Test 
  16.     public void testImportGoods() { 
  17.         searchService.importGoods(); 
  18.     } 
  19.  

实行单元测试卷之后,更新 elasticsearch-head 软件网页页面,結果以下:

查询一下 goods 数据库索引库的投射信息内容。

MySQL 的 goods 表:

Elasticsearch 的 goods 数据库索引库:

见到之上結果,表明 MySQL 产品信息已所有导进 Elasticsearch。

检索

下面就需要进到今日的主题风格了,检索作用的完成。要做就做全套,大家应用仿京东模版 Vue 完成一个真实的电子商务检索作用。

检索的领域模型实际上蛮简易的,客户键入要检索的产品关键字递交之后,将关键词搜索、页数信息内容等传往后面台,再相互配合 Spring Data Elasticseach 就可以轻轻松松拿下。

①GoodsRepository.java

依照惯例,DAO 层只承担数据处理方法,一行编码拿下。

 
  1. /** 
  2.  * 分页查询、高亮度查看 
  3.  * 
  4.  * @param query 
  5.  * @return 
  6.  */ 
  7. public SearchHits<Goods> selectGoodsListForPage(NativeSearchQuery query) { 
  8.     return elasticsearchRestTemplate.search(query, Goods.class); 

②PageInfo.java

这类作用毫无疑问全是必须配搭分页查询解决的,封裝一个分页查询目标方便快捷。

 
  1. package com.example.result; 
  2.  
  3. import lombok.Data; 
  4. import lombok.NoArgsConstructor; 
  5.  
  6. import java.io.Serializable
  7. import java.util.List; 
  8.  
  9. /** 
  10.  * @author hello沃德老先生 
  11.  * @微信公众平台 hello沃德老先生 
  12.  * @website https://mrhelloworld.com 
  13.  * @wechat 124059770 
  14.  */ 
  15. @Data 
  16. @NoArgsConstructor 
  17. public class PageInfo<T> implements Serializable { 
  18.  
  19.     private static final long serialVersionUID = 6260163970867016563L; 
  20.     private int currentPage; // 当页 
  21.     private int pageSize; // 每张表明总数 
  22.     private int total; // 总纪录数 
  23.     private int totalPage; // 总页码 
  24.     private int prePage; // 上一页 
  25.     private int nextPage; // 下一页 
  26.     private boolean hasPre; // 是不是有上一页 
  27.     private boolean hasNext; // 是不是有下一页 
  28.     private List<T> result; // 回到結果集 
  29.  
  30.     public PageInfo(int currentPage, int pageSize) { 
  31.         // 当页 
  32.         this.currentPage = currentPage < 1 ? 1 : currentPage; 
  33.         // 每张表明总数 
  34.         this.pageSize = pageSize; 
  35.         // 是不是有上一页 
  36.         this.hasPre = currentPage == 1 ? false : true
  37.         // 是不是有下一页 
  38.         this.hasNext = currentPage == totalPage ? false : true
  39.         // 上一页 
  40.         if (hasPre) { 
  41.             this.prePage = currentPage - 1; 
  42.         } 
  43.         // 下一页 
  44.         if (hasNext) { 
  45.             this.nextPage = currentPage   1; 
  46.         } 
  47.     } 
  48.  
  49.     public PageInfo(int currentPage, int pageSize, int total) { 
  50.         // 当页 
  51.         this.currentPage = currentPage < 1 ? 1 : currentPage; 
  52.         // 每张表明总数 
  53.         this.pageSize = pageSize; 
  54.         // 总纪录数 
  55.         this.total = total; 
  56.         // 测算总页码 
  57.         if (total == 0) { 
  58.             this.totalPage = 0; 
  59.         } else { 
  60.             this.totalPage = (total - 1) / pageSize   1; 
  61.         } 
  62.         // 是不是有上一页 
  63.         this.hasPre = currentPage == 1 ? false : true
  64.         // 是不是有下一页 
  65.         this.hasNext = currentPage == totalPage ? false : true
  66.         // 上一页 
  67.         if (hasPre) { 
  68.             this.prePage = currentPage - 1; 
  69.         } 
  70.         // 下一页 
  71.         if (hasNext) { 
  72.             this.nextPage = currentPage   1; 
  73.         } 
  74.     } 
  75.  

③SearchService.java

Service 领域模型层关键关心下列关键点:

  • 设定高亮度文件格式
  • 搭建检索标准目标:设定检索标准 goodsName 和 keywords;设定高亮度;设定分页查询
  • 启用 DAO 进行检索
  • 解决百度搜索集搭建分页查询目标并回到
 
  1. /** 
  2.  * 检索 
  3.  * 
  4.  * @param searchStr 检索标准 
  5.  * @param pageNum   哪页 
  6.  * @param pageSize  每张表明总数 
  7.  * @return 
  8.  */ 
  9. public PageInfo<GoodsVo> doSearch(String searchStr, Integer pageNum, Integer pageSize) { 
  10.     // 设定高亮度文件格式 <span style='color:red;'></span> 
  11.     HighlightBuilder.Field field = new HighlightBuilder.Field("goodsName"); 
  12.     field.preTags("<span style='color:red;'>"); 
  13.     field.postTags("</span>"); 
  14.     // 搭建检索标准目标 
  15.     BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); 
  16.     // 设定检索标准 goodsName 和 keywords 
  17.     boolQueryBuilder.must(QueryBuilders.multiMatchQuery(searchStr, "goodsName""keywords")); 
  18.     // 搭建检索目标 
  19.     NativeSearchQuery query = new NativeSearchQueryBuilder() 
  20.             .withQuery(boolQueryBuilder) // 检索标准 
  21.             .withHighlightFields(field) // 高亮度 
  22.             .withPageable(PageRequest.of(pageNum - 1, pageSize)) // 分页查询,当页从 0 逐渐 
  23.             .build(); 
  24.     // 检索 
  25.     SearchHits<Goods> searchHits = goodsRepository.selectGoodsListForPage(query); 
  26.     // 总总数 
  27.     Long total = searchHits.getTotalHits(); 
  28.     if (0 > total) { 
  29.         return new PageInfo<>(pageNum, pageSize, 0); 
  30.     } 
  31.     // 复位回到結果集 
  32.     List<GoodsVo> goodsVoList = new ArrayList<>(); 
  33.     // 解决百度搜索集 
  34.     for (SearchHit<Goods> searchHit : searchHits) { 
  35.         // 复位回到結果目标 
  36.         GoodsVo goodsVo = new GoodsVo(); 
  37.         // 获得結果目标 
  38.         Goods goods = searchHit.getContent(); 
  39.         // 复制特性 
  40.         BeanUtils.copyProperties(goods, goodsVo); 
  41.         // 解决高亮度信息内容 
  42.         Map<String, List<String>> highlightFields = searchHit.getHighlightFields(); 
  43.         // 是不是有高亮度信息内容 
  44.         if (highlightFields.containsKey("goodsName")) { 
  45.             String goodsNameHl = highlightFields.get("goodsName").get(0); 
  46.             goodsVo.setGoodsNameHl(goodsNameHl); 
  47.         } else { 
  48.             goodsVo.setGoodsNameHl(goods.getGoodsName()); 
  49.         } 
  50.         goodsVoList.add(goodsVo); 
  51.     } 
  52.     // 复位分页查询目标 
  53.     PageInfo<GoodsVo> pageInfo = new PageInfo<GoodsVo>(pageNum, pageSize, total.intValue()); 
  54.     pageInfo.setResult(goodsVoList); 
  55.     return pageInfo; 

④SearchController.java

操纵层出示一个 /search 的 GET 插口。

 
  1. package com.example.controller; 
  2.  
  3. import com.example.result.PageInfo; 
  4. import com.example.service.SearchService; 
  5. import com.example.vo.GoodsVo; 
  6. import org.springframework.web.bind.annotation.GetMapping; 
  7. import org.springframework.web.bind.annotation.RequestMapping; 
  8. import org.springframework.web.bind.annotation.RestController; 
  9.  
  10. import javax.annotation.Resource; 
  11.  
  12. /** 
  13.  * @author hello沃德老先生 
  14.  * @微信公众平台 hello沃德老先生 
  15.  * @website https://mrhelloworld.com 
  16.  * @wechat 124059770 
  17.  */ 
  18. @RestController 
  19. @RequestMapping("search"
  20. public class SearchController { 
  21.  
  22.     @Resource 
  23.     private SearchService searchService; 
  24.  
  25.     /** 
  26.      * 检索 
  27.      * 
  28.      * @param searchStr 检索标准 
  29.      * @param pageNum   哪页 
  30.      * @param pageSize  每张表明总数 
  31.      * @return 
  32.      */ 
  33.     @GetMapping 
  34.     public PageInfo<GoodsVo> doSearch(String searchStr, Integer pageNum, Integer pageSize) { 
  35.         return searchService.doSearch(searchStr, pageNum, pageSize); 
  36.     } 
  37.  

⑤list.html

在 list.html 中应用 CDN 加上 Vue 和 Axios 免除下载文件的全过程。

 
  1. <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.min.js"></script> 
  2. <script src="https://cdn.jsdelivr.net/npm/axios@0.21.1/dist/axios.min.js"></script> 

⑥网页页面解决

改动头顶部检索(擅于运用 Ctrl F)的表格一部分:

  • 产品关键词的 <input/> 加上 id="searchStr"
  • 加上2个 hidden 种类的 <input/> 用以储放页数信息内容
 
  1. <form action="" name="search" method="get" class="fl" onsubmit="return false;"
  2.     <input id="searchStr" type="text" class="txt" value="输入您产品关键词"/> 
  3.     <input type="submit" class="btn" value="检索"/> 
  4.     <input id="pageNum" type="hidden" value="1"/> 
  5.     <input id="pageSize" type="hidden" value="12"/> 
  6. </form> 

改动产品列表(擅于运用 Ctrl F),只留有一个 <li></li>:

 
  1. <!-- 产品列表 start--> 
  2. <div id="goodsList" class="goodslist mt10"
  3.     <ul> 
  4.         <li> 
  5.             <dl> 
  6.                 <dt><a href=""><img src="images/goods1.jpg" alt=""/></a></dt> 
  7.                 <dd><a href=""><p class="beyondHidden">清华同方精英X2 电脑台式机(2核E3500 2G 500G DVD 键盘鼠标)带20英寸显示屏</p></a></dd> 
  8.                 <dd><strong>¥2399.00</strong></dd> 
  9.                 <dd><a href=""><em>现有10人点评</em></a></dd> 
  10.             </dl> 
  11.         </li> 
  12.     </ul> 
  13. </div> 
  14. <!-- 产品列表 end--> 

改动分页查询信息内容(擅于运用 Ctrl F):

 
  1. <!-- 分页查询信息内容 start --> 
  2. <div id="page" class="page mt20"
  3.     <a href="">主页</a> 
  4.     <a href="">上一页</a> 
  5.     <a href="" class="cur">3</a> 
  6.     <a href="">下一页</a> 
  7.     <a href="">尾页</a>&nbsp;&nbsp; 
  8.     <span> 
  9.         <em>共8页&nbsp;&nbsp;到第<input type="text" id="num" class="page_num"/>页</em> 
  10.         <a href="" class="skipsearch">明确</a> 
  11.     </span> 
  12. </div> 
  13. <!-- 分页查询信息内容 end --> 

⑦Vue and Axios

最先全局性加上一个 div,设定 id="app"。

随后复位 Vue 目标,关联原素,界定部件数据信息和部件方式。

 
  1. <script> 
  2.   var app = new Vue({ 
  3.     // element 的缩写,初始化原素,关联 id 为 app 的 html 编码精彩片段 
  4.     el: '#app'
  5.     // 界定部件数据信息 
  6.     data: { 
  7.       goodsList: [], 
  8.       page: [] 
  9.     }, 
  10.     // 界定部件方式 
  11.     methods: { 
  12.       // 检索 
  13.       doSearch() { 
  14.         axios({ 
  15.           url: "http://localhost:8080/search"
  16.           method: "GET"
  17.           params: { 
  18.             searchStr: $("#searchStr").val(), 
  19.             pageNum: $("#pageNum").val(), 
  20.             pageSize: $("#pageSize").val() 
  21.           } 
  22.         }).then(response => { // 回到結果 
  23.           $("#pageNum").val(1);// 重置当前页 
  24.           if (response.data.total <= 0) { 
  25.             $('#goodsList').append('<strong>抱歉,沒有寻找与“'   $("#searchStr").val()   '”有关的产品,请确定关键词搜索是不是恰当。</strong>'); 
  26.           } 
  27.           this.goodsList = response.data.result; 
  28.           this.page = response.data; 
  29.         }).catch(error => {// 出现异常捕捉 
  30.           alert('系统软件正在升级中,请稍后再试!'); 
  31.         }); 
  32.       }, 
  33.       // 上一页 
  34.       prePage() { 
  35.         // 获得当页的值并减一,随后再次取值给当页 
  36.         let page = parseInt($("#pageNum").val()) - 1; 
  37.         $("#pageNum").val(page); 
  38.         // 启用检索涵数 
  39.         this.doSearch(); 
  40.       }, 
  41.       // 下一页 
  42.       nextPage() { 
  43.         // 获得当页的值并加一,随后再次取值给当页 
  44.         let page = parseInt($("#pageNum").val())   1; 
  45.         $("#pageNum").val(page); 
  46.         // 启用检索涵数 
  47.         this.doSearch(); 
  48.       }, 
  49.       // 哪页 
  50.       whichPage(num) { 
  51.         // 获得点一下的按键(主页、1、2...尾页)值,随后再次取值给当页 
  52.         $("#pageNum").val(num); 
  53.         // 启用检索函数 
  54.         this.doSearch(); 
  55.       }, 
  56.       // 到哪页 
  57.       goToPage() { 
  58.         // 获得键入的页数值,随后再次取值给当页 
  59.         $("#pageNum").val($("#num").val()); 
  60.         // 启用检索涵数 
  61.         this.doSearch(); 
  62.       } 
  63.     } 
  64.   }); 
  65. </script> 

⑧关联原素

改动头顶部检索(擅于运用 Ctrl F)的表格一部分:加上 v-on:click="doSearch"(缩写方法 @click="doSearch")到检索按键上。

 
  1. <form action="" name="search" method="get" class="fl" onsubmit="return false;"
  2.     <input id="searchStr" type="text" class="txt" value="输入您产品关键词"/> 
  3.     <input v-on:click="doSearch" type="submit" class="btn" value="检索"/> 
  4.     <input id="pageNum" type="hidden" value="1"/> 
  5.     <input id="pageSize" type="hidden" value="12"/> 
  6. </form> 

改动产品列表(擅于运用 Ctrl F):

  • 目录3D渲染:<li></li> 加上 v-for="(goods, index) in goodsList"
  • 商品信息:<img/> 加上 v-bind:src="goods.originalImg"(缩写方法 :src="goods.originalImg")
  • 产品名称并高亮度:<p></p> 加上 v-html="goods.goodsNameHl"
  • 产品价格:加上 {{ goods.marketPrice }}
  • 产品评价:加上 {{ goods.commentCount }}
 
  1. <!-- 产品列表 start--> 
  2. <div id="goodsList" class="goodslist mt10"
  3.     <ul> 
  4.         <li v-for="(goods, index) in goodsList"
  5.             <dl> 
  6.                 <dt><a href=""><img v-bind:src="goods.originalImg" alt=""/></a></dt> 
  7.                 <dd><a href=""><p class="beyondHidden" v-html="goods.goodsNameHl"></p></a></dd> 
  8.                 <dd><strong>¥{{ goods.marketPrice }}</strong></dd> 
  9.                 <dd><a href=""><em>现有{{ goods.commentCount }}人点评</em></a></dd> 
  10.             </dl> 
  11.         </li> 
  12.     </ul> 
  13. </div> 
  14. <!-- 产品列表 end--> 

改动分页查询信息内容(擅于运用 Ctrl F):

  • 主页:v-on:click="whichPage(1)"
  • 尾页:v-on:click="whichPage(page.totalPage)"
  • 上一页:v-if="page.hasPre" v-on:click="prePage"
  • 下一页:v-if="page.hasNext" v-on:click="nextPage"
  • 某一页:循环系统3D渲染:v-for="i in page.totalPage";某一页:v-on:click="whichPage(i)";当页款式解决:v-bind:class="{ cur:i == page.currentPage }"
  • 共是多少页:{{ page.totalPage }}
  • 到哪页:v-on:click="goToPage"
 
  1. <!-- 分页查询信息内容 start --> 
  2. <div id="page" class="page mt20"
  3.     <a v-on:click="whichPage(1)" href="javascript:void(0);">主页</a> 
  4.     <a v-if="page.hasPre" v-on:click="prePage" href="javascript:void(0);">上一页</a> 
  5.     <a v-for="i in page.totalPage" 
  6.        v-on:click="whichPage(i)" 
  7.        v-bind:class="{ cur:i == page.currentPage }" 
  8.        href="javascript:void(0);">{{ i }}</a> 
  9.     <a v-if="page.hasNext" v-on:click="nextPage" href="javascript:void(0);">下一页</a> 
  10.     <a v-on:click="whichPage(page.totalPage)" href="javascript:void(0);">尾页</a>&nbsp;&nbsp; 
  11.     <span> 
  12.         <em>共{{ page.totalPage }}页&nbsp;&nbsp;到第<input type="text" id="num" class="page_num"/>页</em> 
  13.         <a v-on:click="goToPage" href="javascript:void(0);" class="skipsearch">明确</a> 
  14.     </span> 
  15. </div> 
  16. <!-- 分页查询信息内容 end --> 

检测

浏览:http://localhost:8080/list.html 随意键入哪些测试一下吧。

总结

到此 Elasticsearch 的实战演练好项目《电商搜索系统》就进行啦,文中解读了 Spring Boot 融合 Elasticsearch 的应用,顺带融合前面 Vue 完成了网页页面实际效果。

做为一款十分受欢迎的百度搜索引擎,大伙儿十分必须开展更深层次的学习培训,最终祝大家涨薪!涨薪!涨薪!

要想一步到位立即获得编码的同学们:

 
  1. 连接:https://pan.baidu.com/s/1VfRjcHCtTFo1-y-nwhmLvQ 
  2. 提取码:abcd 

参考文献:

  • 《这就是搜索引擎:核心技术详解》张俊林著
  • https://docs.spring.io/spring-data/elasticsearch/docs/4.1.5/reference/html/#reference
  • https://cn.vuejs.org/v2/guide/index.html
  • https://github.com/axios/axios

创作者:hello沃德老先生

编写:陶家龙

出處:转载微信公众号hello沃德老先生(ID:imrhelloworld)

the end
免责声明:本文不代表本站的观点和立场,如有侵权请联系本站删除!本站仅提供信息存储空间服务。