网站首页 > 开源技术 正文
今天要聊的就是「博客管理」中全文搜索的实现,基于 SpringBoot+Vue+ES 实现,先给大家看一下效果:
全文搜索+关键字高亮,是不是和百度的效果差不多,话不多说,直接聊如何实现。
该网站前端是仿一个开源项目,本人主要是做后端,所以本教程涉及前端的部分会直接给代码,不做深入讲解
数据准备
首先我们的数据是存放在 Mysql 的,建表语句如下,再利用 Mybatis-Plus 实现普通查询,比较简单,也不是本文重点,不做赘述。
create table code_note
(
id bigint not null auto_increment,
title varchar(128) not null default '' comment '标题',
md_content text comment 'md文本',
html_content text comment 'html文本',
summary varchar(256) comment '摘要',
category varchar(10) comment '分类',
type tinyint not null default 0 comment '类型:0-文字;1-视频',
create_time datetime not null comment '创建时间',
publish_time datetime not null comment '发布时间',
status tinyint DEFAULT 0 COMMENT '0-草稿箱;1-已发表;2-已删除',
primary key (id)
);
INSERT INTO yitiao_admin.code_note (id, title, author, md_content, html_content, summary, category, type, create_time, publish_time, status) VALUES (14, '一条', 'yitiao', 'canal', '<p>canal</p>
', null, '默认', null, '2023-01-30 10:28:17', '2023-01-30 10:28:17', 1);
复制代码
前端页面
前端是基于 element-ui 来实现的,从文档找了半天,决定用 table 来实现,如果有更好的实现方式可以评论区留言。
其实就是只有的一列的无边框的表格,表格内又嵌入文本和按钮,再就是一些样式的调整,关键代码如下:
<el-table
:data="searchTableData"
v-if="searchShow"
>
<el-table-column
label=""
width="800"
border
>
<template #default="scope">
<div style="width: 100%">
<el-button type="text" size="medium" style="border:none;font-size: large" @click="details(scope.row)">
<span v-html="scope.row.esTitle"></span>
</el-button>
<div style="margin-top: 5px;font-size: medium" v-html="scope.row.esContent"></div>
<div style="margin-top: 5px">
<span>{{ scope.row.author }}</span>
<span style="margin-left: 10px">{{ scope.row.createTime }}</span>
</div>
</div>
</template>
</el-table-column>
</el-table>
复制代码
「查询」和「全文搜索」按钮的切换使用的 v-if="searchShow",向后端发请求的部分如下:
fullSearch() {
this.searchShow = true;
this.pageShow = false;
if (this.search === '' || this.search === null) {
this.search = 'spring'
}
request.get("/es/note/getByContent/", {
params: {
// pageNum: this.currentPage,
// pageSize: this.pageSize,
content: this.search
}
}).then(res => {
console.log(res)
this.searchTableData = res.data
})
}
复制代码
Docker安装ES
终于要到正题啦,因为ES非常的耗内存,我的服务器剩余内存只有不到2G,所以选择用Docker部署单机版的ES。
sudo docker pull elasticsearch:7.12.0
## 创建挂载目录 config、data、plugins,开启全部权限
chmod -R 777 /data/opt/es
## 创建配置文件 cd config
vim elasticsearch.yml
http.host: 0.0.0.0
## 启动容器
sudo docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms84m -Xmx512m" \
-v /data/opt/es/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /data/opt/es/data:/usr/share/elasticsearch/data \
-v /data/opt/es/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.12.0
# 查看日志
docker logs elasticsearch
复制代码
测试正常启动页面:http://101.43.138.173:9200/
插件使用
集群黄色解决
我们的elasticsearch是单节点的,只有一个主服务没有从服务,也就是说所以最简单的办法就是在创建索引的时候将备份数改为0。
如果我们已经创建了索引,那么我们可以直接更改索引的备份数方法举例如下:
## 请求方式为put
## url地址解释:IP地址:端口/索引名称/_settings(_settings 是接口的固定用法)
curl -X PUT -H "Content-Type: application/json" -d '{"number_of_replicas":0}' http://101.43.138.173:9200/code_note/_settings --user name:password
## 返回 {"acknowledged":true}
复制代码
刷新插件,集群变成绿色。
设置用户名密码
# vim elasticsearch.yml
http.cors.enabled: true
http.cors.allow-origin: "*"
http.cors.allow-headers: Authorization
xpack.security.enabled: true
xpack.security.transport.ssl.enabled: true
复制代码
docker exec -it fa41ca453d06 /bin/bash
./bin/elasticsearch-setup-passwords interactive
## 输入密码
复制代码
设置成功后,用户名为elastic,密码为设置的值,同时es里多了一个索引:.security-7
安装分词器
下载,版本一定要和es的对应,安装时注意,并不是一解压就好了。
首先查看插件的名字,解压后打开plugin-descriptor.properties文件,查看插件的名字,然后在挂载的plugins文件夹下新建文件夹,以插件的名字命名。
再将解压出来文件全部移动到插件名文件夹下才可以。
重启ES,查看日志
docker restart fa41ca453d06
docker logs fa41ca453d06
复制代码
至此,ES服务端部署完成,接下来就是基于SpringBoot操作ES。
Java客户端
spring-boot-starter-data-elasticsearch是比较好用的一个elasticsearch客户端,它内部会引入spring-data-elasticsearch。
版本对应关系
如果使用spring-boot-starter-data-elasticsearch,需要调整spring-boot的版本才起作用。
有下边这几种方法操作ElasticSearch:
- ElasticsearchRepository(传统的方法,可以使用)
- ElasticsearchRestTemplate(推荐使用。基于RestHighLevelClient)
- ElasticsearchTemplate(ES7中废弃,不建议使用。基于TransportClient)
- RestHighLevelClient(推荐度低于ElasticsearchRestTemplate,因为API不够高级)
- TransportClient(ES7中废弃,不建议使用)
案例代码
配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
<version>2.7.7</version>
</dependency>
复制代码
spring:
elasticsearch:
rest:
uris: 101.43.138.173:9200 # 多个用逗号隔开
# username: ---用户名
# password: ---密码
connection-timeout: 1000 # 连接超时时间
read-timeout: 1000 # 读取超时时间
复制代码
索引类
// 省略部分字段
@Data
@Document(indexName = "code_note")
@Setting(replicas = 0) // 副本为0,单机模式
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class EsCodeNote {
@Id
private Long id;
/**
* md文本
*/
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String mdContent;
/**
* 分类
*/
@Field(type = FieldType.Keyword)
private String category;
/**
* 创建时间
*/
@Field(type = FieldType.Date, format = DateFormat.custom,
pattern = "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis")
private Date createTime;
}
复制代码
mapper类
@Repository
public interface CodeNoteRepository extends ElasticsearchRepository<EsCodeNote, String> {
}
复制代码
service层
@Service
@Slf4j
@RequiredArgsConstructor
public class CodeNoteService {
private final ElasticsearchRestTemplate esRestTemplate;
private final CodeNoteRepository codeNoteRepository;
private final CodeNoteMapper noteMapper;
public Object saveNoteToEs(EsCodeNote codeNote){
return codeNoteRepository.save(codeNote);
}
public void saveNotesToEs(List<EsCodeNote> codeNotes){
codeNoteRepository.saveAll(codeNotes);
}
public List<EsCodeNote> getFromEsByContent(String content) {
//高亮
String preTag = "<font color='red'>";
String postTag = "</font>";
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder().should(new MatchQueryBuilder("mdContent", content));
Query query = new NativeSearchQueryBuilder()
.withQuery(boolQueryBuilder)
.withHighlightFields(new HighlightBuilder.Field("mdContent").preTags(preTag).postTags(postTag)).build();
// Query query1 = new NativeSearchQueryBuilder()
// .withQuery(QueryBuilders.multiMatchQuery(content,"content","content.inner")).build();
// .withQuery(QueryBuilders.queryStringQuery(content)).build();
SearchHits<EsCodeNote> search = esRestTemplate.search(query, EsCodeNote.class);
return search.stream().map(SearchHit::getContent).collect(Collectors.toList());
}
public void init() {
List<CodeNote> codeNotes = noteMapper.selectList(Wrappers.lambdaQuery(CodeNote.class));
List<EsCodeNote> esCodeNotes = BeanUtil.copyToList(codeNotes, EsCodeNote.class);
this.saveNotesToEs(esCodeNotes);
}
}
复制代码
controller
@RestController
@RequestMapping("/es")
@Slf4j
@RequiredArgsConstructor
public class EsRestController {
private final CodeNoteService noteService;
@PostMapping("/init")
public Result<Object> createIndex() {
noteService.init();
return Result.success("init all notes success");
}
@GetMapping("/note/getByContent")
public Result<List<EsCodeNote>> getByContent(@RequestParam("content")String content) {
return Result.success(noteService.getFromEsByContent(content));
}
}
复制代码
测试
先初始化全部数据
根据mdContent分词查询
至此后端的高亮查询已经实现,如果与前端结合,还需要对查询结果做进一步封装和处理。
前后端联调
后端构建返回VO
public class EsCodeNoteRes {
private Long id;
/**
* 题目
*/
private String esTitle;
private String author;
/**
* md文本
*/
private String esContent;
/**
* html文本
*/
private String htmlContent;
// 省略部分
/**
* 发布时间
*/
@JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8")
private Date publishTime;
}
复制代码
对返回的结果封装
SearchHits<EsCodeNote> searchHits = esRestTemplate.search(query, EsCodeNote.class);
return searchHits.stream().map(search -> {
EsCodeNote esCodeNote = search.getContent();
search.getHighlightFields().forEach((k, v) -> {
log.info("highlight key is [{}],content is [{}]", k, v.get(0));
// 分别处理标题和正文
if (k.equals("title")) {
esCodeNote.setTitle(v.get(0));
}
if (k.equals("mdContent")) {
esCodeNote.setMdContent(v.get(0));
}
});
// 如果正文里没有关键字,取前100字符
if (!esCodeNote.getMdContent().contains(postTag)){
esCodeNote.setMdContent(esCodeNote.getMdContent().substring(0,100));
}
return EsCodeNoteRes.builder()
.id(esCodeNote.getId())
.esTitle(esCodeNote.getTitle())
.author(esCodeNote.getAuthor())
.esContent(esCodeNote.getMdContent())
.htmlContent(esCodeNote.getHtmlContent())
.summary(esCodeNote.getSummary())
.category(esCodeNote.getCategory())
.createTime(esCodeNote.getCreateTime())
.publishTime(esCodeNote.getPublishTime())
.build();
}).collect(Collectors.toList());
复制代码
结果展示
道阻且长,行则将至。2023,扬帆起航。
原文链接:https://juejin.cn/post/7194734486327099429
猜你喜欢
- 2024-12-14 ES的searchAfter使用详解
- 2024-12-14 Spring Boot Data Elasticsearch 通用工具类
- 2024-12-14 Spring Data JPA——多表设计、一对多、多对多、多表查询
- 2024-12-14 一文读懂SpringBoot整合Elasticsearch
- 2024-12-14 ElasticSearchRepository和ElasticSearchTemplate的使用
- 2024-12-14 Elasticsearch 在地理信息空间索引的探索和演进
- 2024-12-14 Java微服务-一套前后台全部开源的H5商城送给大家(全部开源)
- 2024-12-14 android使用greendao来保存数据
- 2024-12-14 纯干货,Spring-data-jpa详解,全方位介绍
- 2024-12-14 一套前后台全部开源的H5商城送给大家
你 发表评论:
欢迎- 03-24罕见的PostgreSQL数据库主从物理复制断开案例
- 03-24每周 GitHub 探索|Teale,基于 postgres 的无代码 Airtable 替代方案
- 03-24KVM、QEMU、LIBVIRT 是什么(kvm qemu libvirt)
- 03-24Ukrainian academician and expert team visited Yiheyuan to investigate the crispy pear project
- 03-24这72个一建机电考点每年必考一次,囊括9成考点,背熟实务超线30
- 03-24思南安监部门路边查车(安监可以在路上查证吗)
- 03-242022年一造《管理》+《计价》真题及答案已出,速来看看上岸没有
- 03-24如何在 Linux 中创建 Systemd 服务?
- 最近发表
-
- 罕见的PostgreSQL数据库主从物理复制断开案例
- 每周 GitHub 探索|Teale,基于 postgres 的无代码 Airtable 替代方案
- KVM、QEMU、LIBVIRT 是什么(kvm qemu libvirt)
- Ukrainian academician and expert team visited Yiheyuan to investigate the crispy pear project
- 这72个一建机电考点每年必考一次,囊括9成考点,背熟实务超线30
- 思南安监部门路边查车(安监可以在路上查证吗)
- 2022年一造《管理》+《计价》真题及答案已出,速来看看上岸没有
- 如何在 Linux 中创建 Systemd 服务?
- CentOS 下用 Nginx 和 uwsgi 部署 flask 项目
- 使用Flask应用框架在Centos7.8系统上部署机器学习模型
- 标签列表
-
- jdk (81)
- putty (66)
- rufus (78)
- 内网穿透 (89)
- okhttp (70)
- powertoys (74)
- windowsterminal (81)
- netcat (65)
- ghostscript (65)
- veracrypt (65)
- asp.netcore (70)
- wrk (67)
- aspose.words (80)
- itk (80)
- ajaxfileupload.js (66)
- sqlhelper (67)
- express.js (67)
- phpmailer (67)
- xjar (70)
- redisclient (78)
- wakeonlan (66)
- tinygo (85)
- startbbs (72)
- webftp (82)
- vsvim (79)
本文暂时没有评论,来添加一个吧(●'◡'●)