哈喽,咱们好,我是一条。

2023全新启航,去年时断时续做的小破站也总算能够和咱们碰头了,链接

因为域名审核实在是麻烦,就先用ip访问吧,支撑注册登录,嫌麻烦的小伙伴也能够直接游客登录,体会是相同的。

目前现已完成了许多功用,但还没有彻底开放,网站主页有上线日志。

后边更新的内容也会环绕这个网站的完成来展开,相应的功用会连续开放,咱们能够先保存一下网站的地址

SpringBoot+Vue+ES 实现仿百度全文搜索

今天要聊的便是「博客管理」中全文查找的完成,依据 SpringBoot+Vue+ES 完成,先给咱们看一下作用:

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/

插件运用

SpringBoot+Vue+ES 实现仿百度全文搜索

集群黄色处理

SpringBoot+Vue+ES 实现仿百度全文搜索

咱们的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
## 输入暗码

SpringBoot+Vue+ES 实现仿百度全文搜索

设置成功后,用户名为elastic,暗码为设置的值,一起es里多了一个索引:.security-7

SpringBoot+Vue+ES 实现仿百度全文搜索

装置分词器

下载,版别一定要和es的对应,装置时留意,并不是一解压就好了。

首先检查插件的姓名,解压后打开plugin-descriptor.properties文件,检查插件的姓名,然后在挂载的plugins文件夹下新建文件夹,以插件的姓名命名。

再将解压出来文件悉数移动到插件名文件夹下才能够。

SpringBoot+Vue+ES 实现仿百度全文搜索

重启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的版别才起作用。

SpringBoot+Vue+ES 实现仿百度全文搜索

有下边这几种方法操作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>";
        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));
    }
}

测验

先初始化悉数数据

SpringBoot+Vue+ES 实现仿百度全文搜索

依据mdContent分词查询

SpringBoot+Vue+ES 实现仿百度全文搜索

至此后端的高亮查询现已完成,假如与前端结合,还需要对查询成果做进一步封装和处理。

前后端联调

后端构建回来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());

成果展示

SpringBoot+Vue+ES 实现仿百度全文搜索

总结

至此,仿百度全文查找现已完成,一条在做这块的时分前后也是调试了好几天,搞下来成就感满满。

本文没有供给悉数代码,意在期望咱们自己动手搞一搞,对一个完整的前后端需求有全面的认知,一起感兴趣的也能够深化的了解一下ES。

本文还没处理的问题便是后边文章「新增修正删去」时如何实时的同步到ES,所以下一期将会聊一下「依据Canal完成MySql到ES的实时同步

道阻且长,行则将至。2023,扬帆起航