聚合搜索系统

image-20240330204744024

前端

  • vue3
  • ant design vue

后端

项目初始化

第一期

前端,后端初始化

前端页面开发,后端基本搜索接口

第二期

数据抓取

聚合搜索接口开发

  • 适配器
  • 门面模式

elasticsearch 搭建

第三期

elasticsearch使用 建表,读写数据,调用api,Java整合

数据同步(四种同步方式)

第四期

保障接口稳定性

项目优化(关键词高亮,搜索建议,防抖节流)

先鸽一下。


第一期

前端初始化

后端初始化

前端页面开发,后端基本搜索接口

整合Axios

//这里注意看官方开发文档,跟着走
npm install -g @vue/cli
//创建项目
vue create antd-demo
#使用组件
npm i --save ant-design-vue@4.x

后端采用初始模板,根据需求更改sql


前端瘦身,留下主页 indexPage

拆解页面

margin: 0 auto,

max-width: 1024px

用url记录页面搜索状态,当用户刷新页面时,能够还原之前的搜索状态

url <==> 页面状态

核心技巧: 把同步状态改成单向,即只允许url来改变页面状态

  1. 让用户操作时,改变url地址(点击搜索框时,进行填充,切换tab也要填充)
  2. 当url地址改变时,去改变页面状态(监听url的改变)

首先设计动态路由 搜索和切换时参数记得别掉了

watchEffect 实现监听


watchEffect(() => {
searchValue.value ={
...initSearchValue, //兜底
text: route.query.text
}
})

联调后端

引入axios

npm install axios

构建实例

const instance = axios.create({
baseURL: "http://localhost:9999/api",//添加前缀
timeout: 1000000,
headers: {
// "Content-Type": "application/json",
},
}); // 创建axios实例

创建一个全局拦截器

import axios from "axios";

const instance = axios.create({
baseURL: "http://localhost:9999/api",
timeout: 1000000,
headers: {
// "Content-Type": "application/json",
},
}); // 创建axios实例

// 添加请求拦截器
instance.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});

// 添加响应拦截器
instance.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
const data = response.data;
if (data.code === 0) {
return response.data;
} else {
return Promise.reject(data);
}
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error);
});
export default instance; // 导出实例


引入文章查询接口

:::后续补充父组件向子组件传入数据的知识

目前简单使用

// 父亲组件调用子组件且传递数据过去 
<postList :post-list="postPageList"/>
//
// 子组件
const props = defineProps({
postList: {
type: Array,
required: true
}
});
// 接收

第二期

  1. 获取多种不同类型的数据源

​ a. 文章(内部)

​ b. 用户(内部)

​ c. 图片 (外部,不是自己项目或者用户生成的数据)

​ d. 后面总结完加个视频(爬取)

  1. 前后端搜索接口联调,跑通页面
  2. 分析现有问题 = 》 优化, 聚合接口开发
  3. 安装es

获取数据源

1、获取文章

===> 爬虫 (悠着点)

抓取网站:xxxx.com

获取到文章后要入库(定时获取或者一次性)

感觉分情况而论,要是查的不严就可以先搜现爬

数据抓取
  1. 直接请求数据接口(最方便)✔
  2. 等网页渲染出明文内容后,从前端页面的内容抓取
  3. 动态请求,类似于谷歌校验人机=>无头浏览器: selenium, node.js puppeteer

引入工具库 HttpClient, OKHttp ,RestTemplate, Hutool(推荐),

数据抓取 ✔

1.分析数据源

2.拿到数据后,如何处理

3.存入数据库

@Slf4j
public class crawlerJob implements CommandLineRunner {

@Resource
private PostService postService;

@Resource
private PostEsDao postEsDao;

@Override
public void run(String... args) {
String json = "{\"current\":1,\"pageSize\":8,\"sortField\":\"createTime\",\"sortOrder\":\"descend\",\"category\":\"文章\",\"tags\":[],\"reviewStatus\":1}";
String url = "https://www.code-nav.cn/api/post/search/page/vo";
String result = HttpRequest.post(url)
.body(json)
.execute().body();
System.out.println(result);
Map<String,Object> data= JSONUtil.toBean(result,Map.class);
System.out.println(data);
List<Post>postList=new ArrayList<>();

JSONObject data1 =(JSONObject) data.get("data");
JSONArray dataList = data1.getJSONArray("records");
for (Object o : dataList) {
JSONObject record=(JSONObject) o;
Post post =new Post();

post.setTags(record.getJSONArray("tags").toString());
post.setContent(record.getStr("content"));
post.setTitle(record.getStr("title"));
post.setUserId(1774104110152880131L);
System.out.println(post);
postList.add(post);
}
boolean b = postService.saveBatch(postList);
if(b){
log.info("数据爬取,且插入{}条",postList.size());
}else {
log.error("数据插入失败");
}
}
}

2.用户获取

额,自己的就够了,没必要的哥们

3.图片获取 ✔

实时抓取,我们网站不存这些数据,用户要搜的时候,直接从别人的网站抓取

获取到html文档,然后从中解析出需要的字段

jsoup库 —》自己看文档..

抓取后,慢慢debug去拨开数据

前后端联调

搞一个pictureController ✔

— 感觉可以狠狠研究一下这个格式(后端工程)

  1. 请求数量比较多,可能会收到浏览器的限制==>用一个接口完成
  2. 请求不同接口的参数不一致,增加前后端沟通成本
  3. 前端要写多个接口调用,重复代码

聚合接口

  1. 请求数量比较多,可能会收到浏览器的限制==>用一个接口完成
  2. 请求不同接口的参数不一致,增加前后端沟通成本=> 用一个接口把请求参数统一,前端每次传固定的参数,后端去对参数进行转换
  3. 前端要写多个接口调用,重复代码=>用一个接口,通过不同参数去区分查询的数据源

撸出一个SearchController ✔

第三期

  1. 聚合接口优化,支持前端更灵活的去取数据
  2. 从0学习Elastic Stack (Elasticsearch)
  3. 学习数据同步,怎么把一个数据库内的数据同步到其他数据库

聚合接口优化

新增 type or category ,通过不同参数去查询不同数据源

  1. 如果category为空,那么搜索出所有数据
  2. 不为空
    • 合法则查询相应数据
    • 否则报错

门面模式

当调用你接口的客户端,觉得麻烦的时候,你就得考虑,是不是得找个门卫了

适配器模式

  1. 定制一个统一的规范(标准)什么数据源允许接入,需要满足哪些标准
  2. 假如说我们数据源已经支持搜索,但是原有的方法和参数和我们的规范不一致

适配器的作用: 通过转换,让两个系统能够完成对接(转换器)

接入我们的系统,必须支持关键词搜索,分页搜索

设计一个接口函数去统一规范,还可以减少后续的if else 的重复代码

@Component
public interface DataSource<T>{

Page<T>doSearch(String searchText,long pageNum,long pageSize);

}

注册器模式(本质也是单例)

  1. 提前通过一个map或者其他容器存储好想要调用的对象

  2. 代码量少,可维护性高

@Component
public class DataSourceRegister {

@Resource
private PictureSource pictureSource;
@Resource
private PostSource postSource;
@Resource
private UserSource userSource;
private Map<String, DataSource<?>> map;

//初始化类时加载
@PostConstruct
public void doInit() {
map = new HashMap<String ,DataSource<?>>() {{
put(SearchEnum.USER.getValue(), userSource);
put(SearchEnum.POST.getValue(), postSource);
put(SearchEnum.PICTURE.getValue(), pictureSource);
}};
}

public DataSource<?> getDataSourceByType(String type) {
return map.get(type);
}
}

//该注解的方法在整个Bean初始化中的执行顺序:

Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的初始化方法)

前端踩坑点!!!

首先加入category之后 不仅需要点击搜索进行发送请求,然后改变tab的时候也需要

const onChangeTab = (category) => {
router.push({
path: `/${category}`,
query: searchValue.value
}).then(() => {
LoadData(searchValue.value)
})
}//使用then,不然会是跳转前的tab。


watchEffect(() => {
searchValue.value = {
...initSearchValue, //兜底
text: route.query.text,
category: route.params.category
}
})

然后就是发现,额之前那个调用LoadAllData()会出问题,md

前端简直就是magic啊

要是watch用的6的话,也可以用这个吧

搜索优化

搜索不够灵活

仅仅靠数据库的like的话

Elastic Stack(一套技术栈)

官网:https://www.elastic.co/

包含了数据的整合=>提取,存储=>使用,一整套

beats: 从各种不同类型的文件/应用来采集数据

Logstash: 从多个采集器或者数据源来抽取/转换数据

Elasticsearch: 存储,查询数据 https://www.elastic.co/guide/en/elasticsearch/reference/7.17/zip-windows.html

kibana: 可视化es的数据 https://www.elastic.co/guide/en/kibana/7.17/windows.html

安装 es kibana

记得版本一致: 7.17

Elasticsearch概念

Index 索引 = > mySql 里面的table

建表 ,crud(查询重点)

用客户端取调用Elasticsearch(3种)

语法: SQL,代码的方法(4种语法(现在学))

能够自动分词?能够高效,灵活的查询内容

索引

正向索引:类似于书籍的目录,可以快速找到对应的内容(根据页码找到文章)

倒排索引:

切词,根据内容找到对应内容

ES的调用方式

  1. restful api (http请求) get /

localhost:9200 给外部用户的接口

localhost:9300 给es集群内部通信的,外部调用不了

  1. kibana devTools(不建议生产环境使用)

    自由地对es进行操作(本质也是resful api)

  2. Java客户端

    https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/7.17/_getting_started.html

kibana:

http://localhost:5601/app/home#

ES的语法:

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-filter-context.html

DSL

建表,插入数据

curl -X POST "localhost:9200/logs-my_app-default/_doc?pretty" -H 'Content-Type: application/json' -d'
{
"@timestamp": "2099-05-06T16:21:15.000Z",
"event": {
"original": "192.0.2.42 - - [06/May/2099:16:21:15 +0000] \"GET /images/bg.jpg HTTP/1.0\" 200 24736"
}
}

查询

GET logs-my_app-default/_search
{
"query": {
"match_all": { }
},
"sort": [
{
"@timestamp": "desc"
}
]
}

删除

DELETE _data_stream/logs-my_app-default

修改

POST mytest/_doc
{
"title": "mytest",
"dec": "there is desc"
}

PUT mytest/_doc/1
{
"title": "mytest",
"dec": "there is desc",
"age": 13
}

SQL

建议使用介个(成本低嘛主要是)


POST /_sql?format=txt
{
"query": "SELECT * FROM mytest"
}

Mapping (数据库表结构)

第四期

  1. 继续ElasticStack

  2. 学习用Java来调用ElasticStack

  3. 使用es优化聚合搜索接口

  4. 已有的DB的数据和ES数据同步(增量,全量,实时,非实时)

  5. jeter压力测试

  6. 保证接口稳定性

  7. 其他拓展思路

ES索引(index) => 表

ES field (字段) => 列

倒排索引

构建倒排索引表:

内容 id
你好 文章 A,B
我是 文章 A,B
rapper 文章 A
鱼皮 文章 B
coder 文章 B

调用语法(DSL,EQL,SQL)

Mapping(表结构)

  • 自动生成mapping

  • 手动指定mapping

分词器

分词的一种规则

空格分词器: whitespace

标准分词

关键词分词器:不分词,将整句话作为专业术语

IK 分词器 (ES插件)

中文友好

下载地址:

怎么样让ik按自己的想法分词?

自定义词典

ik_smart 和ik_max_word 区别?

贪心和滑动窗口(bushi)

打分机制

内容:

  1. 鱼皮是狗
  2. 鱼皮是小黑子
  3. 我是小黑子

用户搜索:

  1. 鱼皮——->第一条分数最高,相当于 2/4(匹配了,而且更短(匹配比例大)

es调用方式(Java

1)ES 官方的 Java API
https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/7.17/introduction.html
快速开始:https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/7.17/connecting.html

2)ES 以前的官方 Java API,HighLevelRestClient(已废弃,不建议用)

3)Spring Data Elasticsearch(推荐)

spring-data 系列:spring 提供的操作数据的框架
spring-data-redis:操作 redis 的一套方法
spring-data-mongodb:操作 mongodb 的一套方法
spring-data-elasticsearch:操作 elasticsearch 的一套方法

官方文档:https://docs.spring.io/spring-data/elasticsearch/docs/4.4.10/reference/html/

自定义方法:用户可以指定接口的方法名称,框架帮你自动生成查询

用es实现搜索接口

1.建立索引(建表)