文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2024-12-03 08:47

关注

[[388762]]

图片来自 Pexels

随着互联网信息爆炸性地飞速增长,网民需要更有效的个性化搜索服务。所以互联网应用几乎没有不开发搜索功能的,既然这个功能这么重要,身为一名合格的程序员必须搞清楚其背后的实现原理。安排!

本文将通过 Spring Boot+Elasticsearch+Vue 实现一个简易版的电商搜索系统,方便大家理解背后的原理。

案例分析

根据上图可以得知,搜索的业务逻辑其实蛮简单的,用户输入要搜索的商品关键词提交以后,将搜索关键词、页码信息等传往后台,后台查询后将结果集进行处理并返回。

需要关注的无非以下几点:

MySQL

核心表(goods)如下:

MySQL 商品表的字段是非常多的,而商城搜索页面所需要的数据无非就是商品ID、商品名称、商品价格、商品评论数和商品原始图等(根据自己实际开发情况而定,这里根据本文案例前端样式进行分析)。

一般商品表还会添加商品关键词字段专门方便搜索引擎使用,所以一条单表 SQL 查询相信应该难不倒各位。

我们先不考虑数据库性能方面的问题,就单纯为了实现这个功能来看看都需要做什么:

WTF,接到这样的需求如果让你用关系型数据库去实现,的确有点强人所难。怎么办?往下看。

Elasticsearch

随着互联网信息爆炸性地飞速增长,传统的查询方法已无法为网民提供有效的搜索服务。

如果我们做的只是用户量很少的内网项目,并且搜索的字段都是一些内容很简短的字段,比如姓名,编号之类的,那完全可以用数据库 like 语句。

但是,数据库 like 查询性能非常低,如果搜索的请求过多,或者需要搜索的是大文本类型的内容(全文搜索),那么这种搜索的方案也是不可取的。

互联网的飞速发展迫切地需求一种快速、全面、准确且稳定可靠的信息查询方法。

既然我们要做性能高的全文搜索,这个需求又不能依赖数据库,只能由我们自己来实现了。

但是令我们很受打击的是全文搜索是很难实现的,我们不仅希望能全文搜索,还希望它足够稳定足够快,且搜索结果有关键字高亮,还能按各种匹配分数来排序,希望它能切换不同的分词算法来满足各种分词需求。

综上所述,如果我们想要做一个功能完善,性能强大的全文搜索其实并非易事,而全文搜索又是一个常见的需求,在这种环境下,市面上出现了一些开源的解决方案。

这些解决方案开源出来后,获得了大量的社区开发者支持,不断为其开发插件,使其不断优化和完善,这就成了我们所说的搜索引擎了。而它们中最有名气的就是 Elasticsearch 和 Solr。

①分词

刚才我们提到的问题中,假设搜索条件是华为手机平板电脑,要求是只要满足了其中任意一个词语组合的数据都要查询出来。

借助 Elasticseach 的文本分析功能可以轻松将搜索条件进行分词处理,再结合倒排索引实现快速检索。

Elasticseach 提供了三种分词方法:

单字分词:

二分法分词:

词库分词:按某种算法构造词,然后去匹配已建好的词库集合,如果匹配到就切分出来成为词语。

通常词库分词被认为是最理想的中文分词算法。而词库分词最常用的就是 IK 分词。

IK 分词器提供两种分词模式:

②倒排索引

对于搜索引擎来讲:

倒排索引的查询流程是:首先根据关键字搜索到对应的文档 ID,然后根据正排索引查询文档 ID 的完整内容,最后返回给用户想要的结果。

③组成部分

倒排索引是搜索引擎的核心,主要包含两个部分:

倒排索引项(Posting)主要包含如下的信息:

④案例

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

假设文档集合包含五个文档,每个文档内容如下图所示。在图中最左端一栏是每个文档对应的文档编号。我们的任务就是对这个文档集合建立倒排索引。

首先用分词系统将文档自动切分成单词序列。为了系统后续处理方便,需要对每个不同的单词赋予唯一的单词编号,同时记录哪些文档中包含这个单词,在如此处理结束后,我们可以得到最简单的倒排索引,如下图所示。

在图中,“单词 ID”一栏记录了每个单词的单词编号,第二栏是对应的单词,第三栏即每个单词对应的倒排列表。比如单词 谷歌,其单词编号为 1,倒排列表为 1,2,3,4,5,说明文档集合中每个文档都包含了这个单词。

为了方便大家理解,上图只是一个简单的倒排索引,只记录了哪些文档包含哪些单词。而事实上,索引系统还可以记录除此之外的更多信息。

下图则是一个相对复杂的倒排索引,在单词对应的倒排列表中不仅记录了文档编号,还记录了单词频率信息(TF),即这个单词在某个文档中出现的次数。

之所以要记录这个信息,是因为词频信息在搜索结果排序时,计算查询和文档相似度是很重要的一个计算因子,所以将其记录在倒排列表中,以方便后续排序时进行分值计算。

图中单词 创始人 的单词编号为 7,对应的倒排列表内容为 (3;1),其中 3 代表文档编号为 3 的文档包含这个单词,数字 1 代表词频信息,即这个单词在 3 号文档中只出现过 1 次。

实用的倒排索引还可以记录更多的信息,如下图所示索引系统除了记录文档编号和单词频率信息外,额外记载了两类信息,即每个单词对应的“文档频率信息”以及在倒排列表中记录单词在某个文档出现的位置信息(POS)。

有了这个索引系统,搜索引擎可以很方便地响应用户的查询,比如用户输入查询词“Facebook”,搜索系统查找倒排索引,从中读取包含这个单词的文档,这些文档就是提供给用户的搜索结果。

利用单词频率信息、文档频率信息可以对这些候选搜索结果进行排序,计算文档和查询的相似性,按照相似性的得分由高到低排序输出。

综上所述,你懂的,废话不多说,下面进入实战环节。

准备工作

环境:

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. "https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.min.js"
  2. "https://cdn.jsdelivr.net/npm/axios@0.21.1/dist/axios.min.js"

 

 

启动

访问:http://localhost:8080/list.html,效果如下:

功能开发

创建索引

①需求说明

该功能主要用于将 MySQL 商品信息导入 Elasticseach。

如果贵司搭建了 ELK 系统,可以使用 Logstash 将 MySQL 商品信息导入 Elasticseach。这部分内容后期我也会更新,敬请期待,今天我们不聊这部分。

既然没有 Logstash 那我们就通过代码将 MySQL 数据导入 Elasticseach。

MySQL 商品表的字段是非常多的,而商城搜索页面所需要的数据无非就是商品ID、商品名称、商品价格、商品评论数和商品原始图等(根据自己实际开发情况而定,这里根据本文案例前端样式进行分析)。

一般商品表还会添加商品关键词字段专门方便搜索引擎使用,所以一条单表 SQL 查询相信应该难不倒各位。

②实体类

创建实体类 Goods.java,添加 @Document 注解用于映射 Elasticsearch 索引库:

设置索引规则:

  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(indexName = "goods", shards = 5, replicas = 1, createIndex = true
  17. public class Goods implements Serializable { 
  18.  
  19.     private static final long serialVersionUID = -1989082640160920658L; 
  20.     @Id 
  21.     private Integer goodsId; // 商品ID 
  22.     @Field(type = FieldType.Text, analyzer = "ik_max_word") // 分词 
  23.     private String goodsName; // 商品名称 
  24.     @Field(type = FieldType.Text, analyzer = "ik_max_word") // 分词 
  25.     private String keywords; // 商品关键词 
  26.     @Field(type = FieldType.Double
  27.     private BigDecimal marketPrice; // 市场价 
  28.     @Field(type = FieldType.Short) 
  29.     private Short commentCount; // 商品评论数 
  30.     @Field(type = FieldType.Keyword) 
  31.     private String originalImg; // 商品原始图地址 
  32.     private String goodsRemark; // 商品简单描述 
  33.     private String goodsContent; // 商品详细描述,存储商品详情图地址 
  34.     private Integer catId; // 分类ID 
  35.     private Integer extendCatId; // 扩展分类ID 
  36.     private String goodsNo; // 商品编号 
  37.     private Integer clickCount; // 点击数 
  38.     private Short brandId; // 品牌ID 
  39.     private Short storeCount; // 库存数量 
  40.     private Integer weight; // 商品重量,单位:克 
  41.     private BigDecimal shopPrice; // 本店价 
  42.     private BigDecimal costPrice; // 商品成本价 
  43.     private Byte isReal; // 是否为实物 
  44.     private Byte isOnSale; // 是否上架 
  45.     private Byte isFreeShipping; // 是否包邮 0 否, 1 是 
  46.     private Integer onTime; // 商品上架时间 
  47.     private Short sort; // 商品排序 
  48.     private Byte isRecommend; // 是否推荐 
  49.     private Byte isNew; // 是否新品 
  50.     private Byte isHot; // 是否热卖 
  51.     private Integer lastUpdate; // 最后更新时间 
  52.     private Short goodsType; // 商品所属类型ID 
  53.     private Short specType; // 商品规格类型 
  54.     private Integer giveIntegral; // 购买商品赠送积分 
  55.     private Integer exchangeIntegral; // 积分兑换:0 不参与积分兑换 
  56.     private Short suppliersId; // 供货商ID 
  57.     private Integer salesSum; // 商品销量 
  58.     private Byte promType; // 0 普通订单,1 限时抢购, 2 团购, 3 促销优惠 
  59.     private Integer promId; // 优惠活动ID 
  60.     private BigDecimal commission; // 佣金用于分销分成 
  61.     private String spu; // SPU 
  62.     private String sku; // SKU 
  63.  

③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. public interface GoodsMapper { 
  10.  
  11.      
  12.     @Select("select goods_id, goods_name, keywords, market_price, comment_count, original_img from goods"
  13.     List selectGoodsList(); 
  14.  

④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. @Repository 
  16. public class GoodsRepository { 
  17.  
  18.     @Resource 
  19.     private ElasticsearchRestTemplate elasticsearchRestTemplate; 
  20.  
  21.     // 是否需要创建索引和设置映射。默认为 false 
  22.     @Value("${search.index.enabled}"
  23.     private boolean indexEnabled; 
  24.  
  25.      
  26.     public void save(List goodsList) { 
  27.         // 是否需要创建索引和设置映射 
  28.         if (indexEnabled) { 
  29.             createIndexAndPutMapping(); 
  30.         } 
  31.         // 批量新增 
  32.         elasticsearchRestTemplate.save(goodsList); 
  33.     } 
  34.  
  35.      
  36.     public void save(Goods goods) { 
  37.         save(Arrays.asList(goods)); 
  38.     } 
  39.  
  40.      
  41.     private void createIndexAndPutMapping() { 
  42.         // 设置索引信息(实体类) 
  43.         IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(Goods.class); 
  44.         // 创建索引 
  45.         indexOperations.create(); 
  46.         // 创建映射 
  47.         Document mapping = indexOperations.createMapping(); 
  48.         // 将映射写入索引 
  49.         indexOperations.putMapping(mapping); 
  50.     } 
  51.  

⑤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. @Service 
  11. public class SearchService { 
  12.  
  13.     @Resource 
  14.     private GoodsMapper goodsMapper; 
  15.     @Resource 
  16.     private GoodsRepository goodsRepository; 
  17.  
  18.      
  19.     public void importGoods() { 
  20.         goodsRepository.save(goodsMapper.selectGoodsList()); 
  21.     } 
  22.  

⑥单元测试

对于当前案例而言,这部分功能没必要暴露一个接口去调用,直接内部编写单元测试完成即可。

  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. public SearchHits selectGoodsListForPage(NativeSearchQuery query) { 
  3.     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. @Data 
  11. @NoArgsConstructor 
  12. public class PageInfo implements Serializable { 
  13.  
  14.     private static final long serialVersionUID = 6260163970867016563L; 
  15.     private int currentPage; // 当前页 
  16.     private int pageSize; // 每页显示条数 
  17.     private int total; // 总记录数 
  18.     private int totalPage; // 总页数 
  19.     private int prePage; // 上一页 
  20.     private int nextPage; // 下一页 
  21.     private boolean hasPre; // 是否有上一页 
  22.     private boolean hasNext; // 是否有下一页 
  23.     private List result; // 返回结果集 
  24.  
  25.     public PageInfo(int currentPage, int pageSize) { 
  26.         // 当前页 
  27.         this.currentPage = currentPage < 1 ? 1 : currentPage; 
  28.         // 每页显示条数 
  29.         this.pageSize = pageSize; 
  30.         // 是否有上一页 
  31.         this.hasPre = currentPage == 1 ? false : true
  32.         // 是否有下一页 
  33.         this.hasNext = currentPage == totalPage ? false : true
  34.         // 上一页 
  35.         if (hasPre) { 
  36.             this.prePage = currentPage - 1; 
  37.         } 
  38.         // 下一页 
  39.         if (hasNext) { 
  40.             this.nextPage = currentPage + 1; 
  41.         } 
  42.     } 
  43.  
  44.     public PageInfo(int currentPage, int pageSize, int total) { 
  45.         // 当前页 
  46.         this.currentPage = currentPage < 1 ? 1 : currentPage; 
  47.         // 每页显示条数 
  48.         this.pageSize = pageSize; 
  49.         // 总记录数 
  50.         this.total = total; 
  51.         // 计算总页数 
  52.         if (total == 0) { 
  53.             this.totalPage = 0; 
  54.         } else { 
  55.             this.totalPage = (total - 1) / pageSize + 1; 
  56.         } 
  57.         // 是否有上一页 
  58.         this.hasPre = currentPage == 1 ? false : true
  59.         // 是否有下一页 
  60.         this.hasNext = currentPage == totalPage ? false : true
  61.         // 上一页 
  62.         if (hasPre) { 
  63.             this.prePage = currentPage - 1; 
  64.         } 
  65.         // 下一页 
  66.         if (hasNext) { 
  67.             this.nextPage = currentPage + 1; 
  68.         } 
  69.     } 
  70.  

③SearchService.java

Service 业务逻辑层主要关注以下细节:

  1.  
  2. public PageInfo doSearch(String searchStr, Integer pageNum, Integer pageSize) { 
  3.     // 设置高亮格式 'color:red;'> 
  4.     HighlightBuilder.Field field = new HighlightBuilder.Field("goodsName"); 
  5.     field.preTags(""); 
  6.     field.postTags(""); 
  7.     // 构建搜索条件对象 
  8.     BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); 
  9.     // 设置搜索条件 goodsName 和 keywords 
  10.     boolQueryBuilder.must(QueryBuilders.multiMatchQuery(searchStr, "goodsName""keywords")); 
  11.     // 构建搜索对象 
  12.     NativeSearchQuery query = new NativeSearchQueryBuilder() 
  13.             .withQuery(boolQueryBuilder) // 搜索条件 
  14.             .withHighlightFields(field) // 高亮 
  15.             .withPageable(PageRequest.of(pageNum - 1, pageSize)) // 分页,当前页从 0 开始 
  16.             .build(); 
  17.     // 搜索 
  18.     SearchHits searchHits = goodsRepository.selectGoodsListForPage(query); 
  19.     // 总条数 
  20.     Long total = searchHits.getTotalHits(); 
  21.     if (0 > total) { 
  22.         return new PageInfo<>(pageNum, pageSize, 0); 
  23.     } 
  24.     // 初始化返回结果集 
  25.     List goodsVoList = new ArrayList<>(); 
  26.     // 处理搜索结果集 
  27.     for (SearchHit searchHit : searchHits) { 
  28.         // 初始化返回结果对象 
  29.         GoodsVo goodsVo = new GoodsVo(); 
  30.         // 获取结果对象 
  31.         Goods goods = searchHit.getContent(); 
  32.         // 拷贝属性 
  33.         BeanUtils.copyProperties(goods, goodsVo); 
  34.         // 处理高亮信息 
  35.         Map> highlightFields = searchHit.getHighlightFields(); 
  36.         // 是否有高亮信息 
  37.         if (highlightFields.containsKey("goodsName")) { 
  38.             String goodsNameHl = highlightFields.get("goodsName").get(0); 
  39.             goodsVo.setGoodsNameHl(goodsNameHl); 
  40.         } else { 
  41.             goodsVo.setGoodsNameHl(goods.getGoodsName()); 
  42.         } 
  43.         goodsVoList.add(goodsVo); 
  44.     } 
  45.     // 初始化分页对象 
  46.     PageInfo pageInfo = new PageInfo(pageNum, pageSize, total.intValue()); 
  47.     pageInfo.setResult(goodsVoList); 
  48.     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. @RestController 
  14. @RequestMapping("search"
  15. public class SearchController { 
  16.  
  17.     @Resource 
  18.     private SearchService searchService; 
  19.  
  20.      
  21.     @GetMapping 
  22.     public PageInfo doSearch(String searchStr, Integer pageNum, Integer pageSize) { 
  23.         return searchService.doSearch(searchStr, pageNum, pageSize); 
  24.     } 
  25.  

⑤list.html

在 list.html 中使用 CDN 添加 Vue 和 Axios 免去下载文件的过程。

  1. "https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.min.js"
  2. "https://cdn.jsdelivr.net/npm/axios@0.21.1/dist/axios.min.js"

⑥页面处理

修改头部搜索(善于利用 Ctrl+F)的表单部分:

  1. action="" name="search" method="get" class="fl" onsubmit="return false;"
  2.     "searchStr" type="text" class="txt" value="请输入商品关键字"/> 
  3.     "submit" class="btn" value="搜索"/> 
  4.     "pageNum" type="hidden" value="1"/> 
  5.     "pageSize" type="hidden" value="12"/> 
  6.  

修改商品列表(善于利用 Ctrl+F),只留下一个

    1. -- 商品列表 start--> 
    2. "goodsList" class="goodslist mt10"
    3.     
         
      •         
      •  
      •             
         
      •                 
        "">"images/goods1.jpg" alt=""/>
         
      •                 
        "">"beyondHidden">清华同方精锐X2 台式电脑(双核E3500 2G 500G DVD 键鼠)带20英寸显示器

         
      •                 
        ¥2399.00
         
      •                 
        "">已有10人评价
         
      •              
      •         
      •  
      •     
       
     
  • -- 商品列表 end--> 
  • 修改分页信息(善于利用 Ctrl+F):

    1. -- 分页信息 start --> 
    2. "page" class="page mt20"
    3.     "">首页 
    4.     "">上一页 
    5.     "" class="cur">3 
    6.     "">下一页 
    7.     "">尾页   
    8.      
    9.         共8页  到第"text" id="num" class="page_num"/>页 
    10.         "" class="skipsearch">确定 
    11.      
     
  • -- 分页信息 end --> 
  • ⑦Vue and Axios

    首先全局添加一个 div,设置 id="app"。

    然后初始化 Vue 对象,绑定元素,定义组件数据和组件方法。

    1.  

    ⑧绑定元素

    修改头部搜索(善于利用 Ctrl+F)的表单部分:添加 v-on:click="doSearch"(简写方式 @click="doSearch")到搜索按钮上。

    1. action="" name="search" method="get" class="fl" onsubmit="return false;"
    2.     "searchStr" type="text" class="txt" value="请输入商品关键字"/> 
    3.     on:click="doSearch" type="submit" class="btn" value="搜索"/> 
    4.     "pageNum" type="hidden" value="1"/> 
    5.     "pageSize" type="hidden" value="12"/> 
    6.  

    修改商品列表(善于利用 Ctrl+F):

    1. -- 商品列表 start--> 
    2. "goodsList" class="goodslist mt10"
    3.     
         
      •         for="(goods, index) in goodsList"
      •             
         
      •                 
        "">"goods.originalImg" alt=""/>
         
      •                 
        "">"beyondHidden" v-html="goods.goodsNameHl">

         
      •                 
        ¥{{ goods.marketPrice }}
         
      •                 
        "">已有{{ goods.commentCount }}人评价
         
      •              
      •         
      •  
      •     
       
    4.  
    5. -- 商品列表 end--> 

    修改分页信息(善于利用 Ctrl+F):

    1. -- 分页信息 start --> 
    2. "page" class="page mt20"
    3.     on:click="whichPage(1)" href="javascript:void(0);">首页 
    4.     "page.hasPre" v-on:click="prePage" href="javascript:void(0);">上一页 
    5.     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 }} 
    9.     "page.hasNext" v-on:click="nextPage" href="javascript:void(0);">下一页 
    10.     on:click="whichPage(page.totalPage)" href="javascript:void(0);">尾页   
    11.      
    12.         共{{ page.totalPage }}页  到第"text" id="num" class="page_num"/>页 
    13.         on:click="goToPage" href="javascript:void(0);" class="skipsearch">确定 
    14.      
    15.  
    16. -- 分页信息 end --> 

    测试

    访问:http://localhost:8080/list.html 随便输入什么测试一下吧。

    结语

    至此 Elasticsearch 的实战小项目《电商搜索系统》就完成啦,本文讲解了 Spring Boot 整合 Elasticsearch 的使用,顺便结合前端 Vue 实现了页面效果。

    作为一款非常热门的搜索引擎,大家非常有必要进行更深入的学习,最后祝大家加薪!加薪!加薪!

    想要一步到位直接获取代码的同学:

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

    参考资料:

    作者:哈喽沃德先生

    编辑:陶家龙

    出处:转载自公众号哈喽沃德先生(ID:imrhelloworld) 

     

    来源:哈喽沃德先生内容投诉

    免责声明:

    ① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

    ② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

    软考中级精品资料免费领

    • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

      难度     813人已做
      查看
    • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

      难度     354人已做
      查看
    • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

      难度     318人已做
      查看
    • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

      难度     435人已做
      查看
    • 2024年上半年系统架构设计师考试综合知识真题

      难度     224人已做
      查看

    相关文章

    发现更多好内容

    猜你喜欢

    AI推送时光机
    位置:首页-资讯-后端开发
    咦!没有更多了?去看看其它编程学习网 内容吧
    首页课程
    资料下载
    问答资讯