续 完成达内知道的搜索功能

执行搜索业务

上次课我们完成了将mysql中question表中的所有数据复制到ES的操作

现在ES中包含所有mysql中包含的问题

要进行查询直接在查询ES中的信息,查出的信息就是mysql中的

分析实现搜索的思路

用户输入一个关键字

question表中title列包含这个关键字或content列包含这个关键字就查询出来

同时这个问题应该是登录用户提问或其它用户公开的

这样的查询逻辑编写为sql语句如下

SELECT * FROM question
WHERE
(title LIKE '%java%'
OR
content LIKE '%java%')
and
(user_id=11
OR
public_status=1)

如果上面的查询转换到es中进行

查询示意图如下

我们查询结果需要满足下面条件

1.问题的title或者content要包含查询的关键字

2.问题必须是登录用户提问的或是公开状态的

1和2两个条件又是"并且\and"的关系

match(匹配):相当于数据库中的like,进行模糊查询用的,ES中就是匹配指定的分词

term(相等):相当于数据库中的"=",执行判等操作,这里判断是整型数值

should(应该):相当于数据库中的or也就是"或"关系

must(必须):相等于数据库中的and也就是"与"关系

上面的查询逻辑在ES中可以编写为:

### 条件搜索,查询用户11 或者 公开的 同时 标题或者内容中包含Java的问题
POST http://localhost:9200/knows/_search
Content-Type: application/json{"query": {"bool": {"must": [{"bool": {"should": [{"match": {"title": "java"}}, {"match": {"content": "java"}}]}}, {"bool": {"should": [{"term": {"publicStatus": 1}}, {"term": {"userId": 11}}]}}]}}
}

这样的查询不适合再使用SpringData中用方法名来表示查询逻辑的情况了

编写搜索功能的数据访问层

QuestionRepository接口中需要编写上面章节中确定的查询语句

// 数据访问层注解@Repository必须要添加
@Repository
public interface QuestionRepositoryextends ElasticsearchRepository<QuestionVO,Integer> {@Query("{\n" +"    \"bool\": {\n" +"      \"must\": [{\n" +"        \"bool\": {\n" +"          \"should\": [\n" +"          {\"match\": {\"title\": \"?0\"}}, \n" +"          {\"match\": {\"content\": \"?1\"}}]\n" +"        }\n" +"      }, {\n" +"        \"bool\": {\n" +"          \"should\": [\n" +"          {\"term\": {\"publicStatus\": 1}}, \n" +"          {\"term\": {\"userId\": ?2}}]\n" +"        }\n" +"      }]\n" +"    }\n" +"  }")Page<QuestionVO> queryAllByParams(String title, String content,Integer userId, Pageable pageable);}

上面的数据访问层方法需要进行测试才能确保能够正确运行

在保证Nacos和ES运行的情况下

运行测试代码如下

// 测试搜索查询的数据访问层方法
@Test
public void search(){Page<QuestionVO> questions=questionRepository.queryAllByParams("java","java",11, PageRequest.of(0,8));questions.forEach(q-> System.out.println(q));
}

如果运行正常

表示数据访问层一切正常

创建分页信息转换类

编写完数据访问层应该编写业务逻辑层

但是在此之前我们需要先编写实现一个能够将Page转换为PageInfo类型的功能

因为我们的页面前端代码,都是在支持PageInfo类型对象的

如果返回值类型变为Page对象,那么前端代码就会有很多维护工作

如果在业务逻辑层返回前能够将Page转换为PageInfo,那么前端就可以直接复用了

所以要在编写业务逻辑层之前先编写这个转换代码

knows-search模块

创建utils包,包中创建转换类Pages

public class Pages {/*** 将Spring-Data提供的翻页数据,转换为Pagehelper翻页数据对象* @param page Spring-Data提供的翻页数据* @return PageInfo*/public static <T> PageInfo<T> pageInfo(Page<T> page){//当前页号从1开始, Spring-Data从0开始,所以要加1int pageNum = page.getNumber()+1;//当前页面大小int pageSize = page.getSize();//总页数 pagesint pages = page.getTotalPages();//当前页面中数据List<T> list = new ArrayList<>(page.toList());//当前页面实际数据大小,有可能能小于页面大小int size = page.getNumberOfElements();//当前页的第一行在数据库中的行号, 这里从0开始int startRow = page.getNumber()*pageSize;//当前页的最后一行在数据库中的行号, 这里从0开始int endRow = page.getNumber()*pageSize+size-1;//当前查询中的总行数long total = page.getTotalElements();PageInfo<T> pageInfo = new PageInfo<>(list);pageInfo.setPageNum(pageNum);pageInfo.setPageSize(pageSize);pageInfo.setPages(pages);pageInfo.setStartRow(startRow);pageInfo.setEndRow(endRow);pageInfo.setSize(size);pageInfo.setTotal(total);pageInfo.calcByNavigatePages(PageInfo.DEFAULT_NAVIGATE_PAGES);return pageInfo;}
}

编写搜索功能的业务逻辑层代码

业务逻辑层开始编写

先在IQuestionService接口中定义搜索方法

// 按用户输入的关键字进行搜索的业务逻辑层方法
PageInfo<QuestionVO> search(String key,String username,Integer pageNum,Integer pageSize);

QuestionServiceImpl类实现代码如下

@Override
public PageInfo<QuestionVO> search(String key, String username, Integer pageNum, Integer pageSize) {//  根据用户名查询用户对象String url="http://sys-service/v1/auth/user?username={1}";User user=restTemplate.getForObject(url,User.class,username);// 定义分页条件和排序规则的Pageable对象Pageable pageable= PageRequest.of(pageNum-1,pageSize,Sort.Direction.DESC,"createtime");// 调用数据访问层进行查询Page<QuestionVO> page=questionRepository.queryAllByParams(key,key,user.getId(),pageable);// 将page转换为PageInfo返回return Pages.pageInfo(page);
}

业务逻辑层也要测试

之前的服务不需要重启继续运行即可

新启动sys模块

进行测试代码如下

// 测试搜索的业务逻辑层方法
@Test
void testService(){PageInfo<QuestionVO> pageInfo=questionService.search("java","st2",1,8);pageInfo.getList().forEach(q-> System.out.println(q));}

如果代码没问题但是运行失败

可以尝试删除分页条件中的排序参数,再运行试试

如果成功了,就是ES不稳定造成的,需要重启ES甚至重新安装ES才能解决

编写控制层代码

knows-search模块中创建controller包

包中创建QuestionController类

@RestController
@RequestMapping("/v3/questions")
public class QuestionController {@Resourceprivate IQuestionService questionService;@PostMappingpublic PageInfo<QuestionVO> search(String key,Integer pageNum,@AuthenticationPrincipal UserDetails user){Integer pageSize=8;if(pageNum==null){pageNum=1;}PageInfo<QuestionVO> pageInfo=questionService.search(key,user.getUsername(),pageNum,pageSize);// 别忘了返回pageInforeturn  pageInfo;}
}

控制器编写完成

但是在运行之前要将所有微服务项目的支持添加配置完成

完成微服务相关配置

配置网关信息

gateway模块

application.yml文件,添加新的路由信息

- id: gateway-searchuri: lb://search-servicepredicates:- Path=/v3/**

跨域和SpringSecurity放行以及拦截器注册

转回knows-search模块

我们当下项目还没有设置跨域放行和拦截器注册的配置

这些配置可以直接从faq模块复制

建议直接复制security包和Interceptor包,直接粘贴到search模块的相同位置

粘贴后WebConfig类需要修改包的导入

并且删除原有的拦截器注册路径,修改为:

@Override
public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(authInterceptor).addPathPatterns("/v3/questions"  //搜索问题);
}

到此为止

微服务后端搜索功能就完成配置编写完成了

前端显示搜索结果

前端调用思路

我们要先明确前端整体的调用流程

我们现在开发的功能是学生首页执行搜索功能

index_student.html页面搜索框输入内容后点击搜索按钮

我们设计为跳转到search.html页面,然后将用户输入的内容

保存到search.html页面地址栏?之后

search.html页面加载完毕后,按?之后的条件,使用axios向search模块进行异步的搜索查询调用,最后将搜索的结果显示在页面上

学生首页跳转到搜索页面

转到knows-client项目

在index_student.html页面中的搜索输入框的位置修改代码如下

index_student.html的40行附近

<div class="form-inline my-2 my-lg-0" id="searchApp"><input class="form-control form-control-sm mr-sm-2 rounded-pill"type="search" placeholder="Search" aria-label="Search"v-model="key"><!-- ↑↑↑↑↑↑ --><button class="btn btn-sm btn-outline-secondary my-2 my-sm-0 rounded-pill"type="button"@click="search"><!-- ↑↑↑↑↑↑ --><i class="fa fa-search" aria-hidden="true"></i></button>
</div>

我们可以再index_student.html页面的尾部

添加vue代码.用户点击按钮时跳转到search.html

<script>let searchApp=new Vue({el:"#searchApp",data: {key:""},methods:{search:function(){// 这里的功能是跳转到search.html页面,并且携带用户输入的关键字// 1.location.href进行跳转// 2.路径是/search.html?// 3.encodeURI()是能够将中文转换为可以传递的格式// 4.this.key是Vue绑定的用户输入的关键字location.href="/search.html?"+encodeURI(this.key);}}})
</script>

启动或重启client项目

在学生首页输入关键字点击搜索按钮,观察是否能够跳转的设计的路径

创建搜索结果页search.html

上面章节实现跳转效果显示404,因为我们的项目还没有创建search.html

显示结果的格式和讲师任务列表\学生问题列表是一致的

所谓我们建议大家直接复制讲师首页为search.html

将页面中"我的任务"修改为"搜索结果"(也可以修改一下搜索图标)

search.html的183行附近修改

<h4 class="border-bottom m-2 p-2 font-weight-light"><i class="fa fa-search" aria-hidden="true"></i> 搜索结果
</h4>

因为当前页面不再是查询讲师首页信息的页面,所以要修改页面尾部的引用

不再引用index_teacher.js,修改为引用search.js

<script src="js/utils.js"></script>
<script src="js/tags_nav_temp.js"></script>
<script src="js/tags_nav.js"></script>
<script src="js/user_info_temp.js"></script>
<script src="js/user_info.js"></script>
<script src="js/search.js"></script>
<!-- ↑↑↑↑↑↑ ↑↑↑↑↑↑ ↑↑↑↑↑↑  -->
</body>

其实js文件的内容也和讲师js文件逻辑类似

我们可以复制index_teacher.js文件为search.js文件

然后进行一些修改即可完成搜索功能的调用

loadQuestions:function (pageNum) {if(! pageNum){pageNum = 1;}// 这里开始代码的修改 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓// 获得地址栏?之后的内容let key=location.search;if(!key){return;}// 获取?之后的搜索关键字,这里要注意中文的转换key=decodeURI(key.substring(1));// 定义搜索功能提交的表单let form=new FormData();form.append("key",key);form.append("pageNum",pageNum);form.append("accessToken",token);axios({url: 'http://localhost:9000/v3/questions',method: "post",data:form//  修改的代码结束↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑})......

下面可以进行测试

需要启动Nacos\ES\Redis

gateway\sys\faq\auth\search

重启client

登录学生,在学生首页输入关键字

查询出的搜索结果是可以分页的

但是没有图片和标签

显示问题的标签和配图

上面的查询不能实现标签和图片的原因是因为

QuestionVO类中的tags属性没有赋值

之前portal项目中我们是利用Question的tagNames属性来获得对应标签的集合

在当前search模块中,我们也要利用QuestionVO的tagNames属性来获得对应标签的集合

只是所有标签内容的获取要通过Ribbon来调用faq模块获得

转到knows-search模块

在QuestionServiceImpl类中添加一个方法获得对应的标签集合

并在搜索功能的方法中来调用

具体代码如下

@Override
public PageInfo<QuestionVO> search(String key, String username, Integer pageNum, Integer pageSize) {//  根据用户名查询用户对象String url="http://sys-service/v1/auth/user?username={1}";User user=restTemplate.getForObject(url,User.class,username);// 定义分页条件和排序规则的Pageable对象Pageable pageable= PageRequest.of(pageNum-1,pageSize,Sort.Direction.DESC,"createtime");// 调用数据访问层进行查询Page<QuestionVO> page=questionRepository.queryAllByParams(key,key,user.getId(),pageable);// 新增的for循环↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓// 遍历所有问题新增tags属性for(QuestionVO vo:page){vo.setTags(tagNamesToTags(vo.getTagNames()));}// 新增代码结束 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑// 将page转换为PageInfo返回return Pages.pageInfo(page);
}
// 新增的方法↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// 编写一个根据tagNames属性返回对应的List<Tag>对象的方法
private List<Tag> tagNamesToTags(String tagNames){// tagNames:"Java基础,Java SE,面试题"String[] names=tagNames.split(",");// 获得包含所有标签的map,以便通过标签名称获得标签对象// 但是faq模块只有获得所有标签对象List的Rest接口// 所以我们先获得List(数组)在转换为MapString url="http://faq-service/v2/tags";Tag[] tagArr=restTemplate.getForObject(url,Tag[].class);// 创建一个Map,遍历数组向启动赋值即可Map<String,Tag> tagMap=new HashMap<>();for(Tag t:tagArr){tagMap.put(t.getName(),t);}// 实例化一个空List,用户保存标签名称对应的标签对象List<Tag> tags=new ArrayList<>();for(String name : names){tags.add(tagMap.get(name));}return tags;}

重启search模块

建议重新登录学生,到学生首页进行搜索

搜索结果中就能显示所有标签和配图了

消息队列

下载Kafka

上面章节为止

我们已经完成了搜索整体功能四个阶段中的3个

1.数据同步

2.按关键字搜索

3.显示搜索结果

4. 学生发布问题时,将该问题同时新增到ES(未完成)

学生发布问题新增到ES的性能问题

我们要完成新增问题时将这个问题也新增到ES的功能

需要两个模块的协作,

1.faq模块完成mysql的新增

2.search模块完成ES的新增

实现功能的思路变化如图

右侧的图是添加了新增问题同时新增到ES的业务流程

如果使用我们现在为止学习的技术,跨微服务调用只能使用Ribbon

但是faq模块的线程运行到这个位置时,需要等待Ribbon做出响应才能继续后面的内容,这样faq模块的线程就会进入阻塞状态

而进入阻塞的时间内,这个线程无法释放资源也无法进行其它操作,造成了资源和性能的浪费

如果要解决这个问题主要就是要消除faq模块调用Ribbon之后的等待时间

上图使用消息队列

faq模块将要新增的信息发送给消息队列

faq模块不需要等待search模块完成ES的新增,就能向前端做出响应,实现释放当前线程占用的资源,用于接受后面的请求

这样做就消除了faq模块的等待,提高了运行效率

什么是消息队列

消息队列(Message Queue)检查MQ

一般情况下用于代替等待时间较长的Ribbon请求

Ribbon请求必须是等待目标有响应之后才能继续运行的

但是消息队列是采用"异步(两个微服务项目并不需要同时完成请求)"的方式来传递数据

faq模块不等待search模块做出响应,就可以完成自己响应了!

消息队列特征

面试题:如何理解消息队列

  • 利用异步的特性,提高服务器的运行效率,减少线程阻塞的时间
  • 削峰填谷:在并发峰值的瞬间将信息保存到消息队列中,依次处理,不会因短时间需要处理大量请求而出现意外,在并发较少时在依次处理队列中的内容,直至处理完毕
  • 消息队列的弊端:因为是异步执行,faq模块完成响应时,search模块可能还没有运行,这样的话就可能出现延迟的现象,如果不能接受这种延迟,就不要使用消息队列

我们开发中常见的消息队列有:

  • ActiveMQ
  • RabbitMQ
  • RocketMQ(阿里)
  • Kafka

kafka简介

Kafka是由Apache软件基金会开发的一个开源流处理平台,由Scala和Java编写。该项目的目标是为处理实时数据提供一个统一、高吞吐、低延迟的平台。Kafka最初是由LinkedIn开发,并随后于2011年初开源。

kafka的软件结构

Producer: 消息的发送方,既消息的来源,是生产者

​ faq就是消息的发送方

Consumer:消息的接收方,既消息的去处,是消费者

​ search模块就是消息的接收方

Topic:就是话题或主题的意思,消息的发送方和接收方需要统一一个话题名称,才能不会错误的将消息发送给其它人,或错误的接收其它人的信息

Record:消息记录,就是生产者和消费者传递的消息,保存在topic中

faq模块和search模块传递的信息就是一个Question对象的json格式字符串

Kafka的安装和启动

将下载的kafka压缩包在根目录解压

路径尽量短,否则运行时报错,路径不要有中文和空格

在当前目录下,再创建一个文件夹,名称随意,但必须是空的

本次创建的目录名称为data,它来保存kafka运行过程中的临时文件和日志文件

它并不需要进行安装操作

下面就可以启动了

kafka软件启动顺序是先启动zookeeper再启动kafka

启动zookeeper

我们kafka压缩包中是包含zookeeper软件的

在运行之前先做一些配置

F:\kafka\config下有文件zookeeper.properties

dataDir=F:/data

F:\kafka\config下有文件server.properties

log.dirs=F:/data

配置完毕之后

打开dos命令行界面

Win+R输入cmd

C:\Users\TEDU>F:F:\>cd F:\kafka\bin\windowsF:\kafka\bin\windows>zookeeper-server-start.bat ..\..\config\zookeeper.properties

再启动kafka

打开命令行Win+R输入cmd

C:\Users\TEDU>F:F:\>cd F:\kafka\bin\windowsF:\kafka\bin\windows>kafka-server-start.bat ..\..\config\server.properties

附录

Mac系统启动Kafka服务命令(参考):

# 进入Kafka文件夹
cd Documents/kafka_2.13-2.4.1/bin/
# 动Zookeeper服务
./zookeeper-server-start.sh -daemon ../config/zookeeper.properties
# 启动Kafka服务
./kafka-server-start.sh -daemon ../config/server.properties

Mac系统关闭Kafka服务命令(参考):

# 关闭Kafka服务
./kafka-server-stop.sh
# 启动Zookeeper服务
./zookeeper-server-stop.sh

在启动kafka时有一个常见错误

wmic不是内部或外部命令

这样的提示,需要安装wmic命令,安装方式参考

https://zhidao.baidu.com/question/295061710.html

英文

Cluster:集群

zookeeper和Kafka

zookeeper简介

zoo keeper

动物园 持有者

动物管理员

很多大数据软件都是动物命名或动物的logo

这些软件在运行前都需要进行一些配置

zookeeper是一个能够统一配置所有软件的配置信息的软件

提供了集中的配置方式,就无需找到对应软件才能配置这个软件了

后期有些软件干脆就不自带配置功能了,只能在zookeeper中配置

kafka就是只能在zookeeper中配置

ProjectDay19相关推荐

最新文章

  1. 一文搞懂TCP的三次握手和四次挥手
  2. 超美观的 Vue+Element 开源后台管理 UI
  3. python sort函数key_Python:s.sort([cmp[, key[, reverse]]])
  4. 学习需要总结。。。。
  5. rocketmq发送顺序消息(四)
  6. php发送https请求,php post 请求https接口
  7. 为什么开不了4g网络_为什么4G网络越来越慢?究竟是你手机出了问题还是网络原因?...
  8. python数据库-mongoDB的高级查询操作(55)
  9. sbit和sfr的定义
  10. Atitit 模块打包器(module bundler)的概念与使用 目录 1. 解决问题 1 1.1. 多js合并方便性能加载 1 1.2. 静态模块打包 2 1.3. 动态模块打包 2 2. 最
  11. Xmind2021绿色版,思维导图最佳软件
  12. 多媒体计算机主要有哪些基本特性,多媒体的特点主要包括哪些?
  13. 单片机(51) 什么是编码器?什么是译码器?
  14. 光盘自动播放 html,插入dvd光盘直接播放 禁用dvd菜单制作刻录dvd光盘 光盘直接放在DVD中就可以自动播放...
  15. SNN识别手写数字—MNIST数据集
  16. Android模拟器的判定
  17. Adobe Creative Cloud一直转圈无法进入的问题
  18. mesh和wifi中继的区别_一文看懂Mesh路由和无线中继的差异
  19. 微信小程序编译时警告:Component “pages/test2/test2“ does not have a method “ getData“ to handle event “t
  20. 如何开放防火墙端口?

热门文章

  1. 来自于微信小程序的一封简讯
  2. css 设置文字隐藏
  3. Excel中如何超链接到另一张表格的匹配数据
  4. 必须学会看官方的英文文档资料等
  5. 二氧化硅干燥剂原理是什么?
  6. Java==和equals的区别以及常量池的内存图解
  7. Edge浏览器移除桔梗网页的方法
  8. 避免骚扰用户?硅谷这样打造移动推送通知!
  9. SqlServer服务启动失败:TDSSNIClient 初始化失败,出现错误 0x139f,状态代码 0x80
  10. 移动端canvas合成图片,填充文字自动换行