基于Spring Boot+Vue的博客系统 15——实现文章搜索功能(分词搜索)

废弃说明:
这个专栏的文章本意是记录笔者第一次搭建博客的过程,文章里里有很多地方的写法都不太恰当,现在已经废弃,关于SpringBoot + Vue 博客系列,笔者重新写了这个系列的文章,不敢说写的好,但是至少思路更加清晰,还在看SpringBoot + Vue 博客系列文章的朋友可以移步:https://blog.csdn.net/li3455277925/category_10341110.html,文章中有错误的地方或者大家有什么意见或建议都可以评论或者私信交流。

需求

在输入框输入一串带有关键词的字符串,后台可以将字符串分成若干个关键词,然后从数据库中查询出带有关键词的文章,在页面上显示,并且显示的时候关键词为红色

后端实现

一开始分词搜素功能使用的是ElasticSearch来实现的,最后发现只用使用一款分词工具就可以实现相同的功能,使用ElasticSearch简直是是大材小用,这里分词工具选用的是HanLP

使用HanLP

HanLP是一系列模型与算法组成的NLP工具包,由大快搜索主导并完全开源,目标是普及自然语言处理在生产环境中的应用。HanLP具备功能完善、性能高效、架构清晰、语料时新、可自定义的特点。
HanLP官网:http://hanlp.com/

  • 引入依赖
<dependency>
    <groupId>com.hankcs</groupId>
    <artifactId>hanlp</artifactId>
    <version>portable-1.7.4</version>
</dependency>
  • ArticleRepository里面添加按照正则表达式查询的方法

注意这里如果使用SQL语句的话,就要将nativeQuery属性设为true,第二个参数Pageable为分页信息

package com.qianyucc.blog.repository;

import com.qianyucc.blog.model.entity.*;
import org.springframework.data.domain.*;
import org.springframework.data.jpa.repository.*;
import org.springframework.data.repository.query.*;

import java.util.*;

/**
 * @author lijing
 * @date 2019-10-11 10:39
 * @description 访问数据库中文章
 */
public interface ArticleRepository extends JpaRepository<ArticleDO, Long>, JpaSpecificationExecutor<ArticleDO> {
    // 正则匹配
    @Query(value = "select * from article where title regexp :regex",nativeQuery = true)
    Page<ArticleDO> findByTitleWithRegex(@Param("regex") String regex, Pageable pageable);
}
  • ArticleService中添加按照查询字符串查询的方法
/**
 * 根据关键词查询文章
 *
 * @param queryString
 * @return
 */
public PageInfoVO<SimpleArticleVO> findArticlesByQueryString(String queryString, Integer pageNumber) {
    // 容错
    pageNumber = pageNumber < 1 ? 0 : pageNumber - 1;

    // 使用HanLP分词
    List<Term> termList = StandardTokenizer.segment(queryString);
    // 拼接正则字符串
    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < termList.size(); i++) {
        String word = termList.get(i).word;
        if (i != termList.size() - 1) {
            sb.append(word + "|");
        } else {
            sb.append(word);
        }
    }
    String regex = sb.toString();
    sb.insert(0, ".*(");
    sb.append(").*");
    // 利用正则查找,这里的函数实现是使用SQL语句查询的所以在Sort里面要使用gmt_update而不能使用gmtUpdate
    Pageable pageable = new PageRequest(pageNumber, PAGE_SIZE, new Sort(Sort.Direction.DESC, "gmt_update"));
    Page<ArticleDO> page = articleRepository.findByTitleWithRegex(sb.toString(), pageable);
    List<ArticleDO> articleDOS = page.getContent();
    // 将标题中关键字替换为红色
    articleDOS.forEach(article -> {
        String title = article.getTitle();
        // 利用正则替换
        String newTitle = title.replaceAll(regex, "<font style='color:red;'>$0</font>");
        article.setTitle(newTitle);
    });

    PageInfoVO<SimpleArticleVO> pageInfoVO = new PageInfoVO<>();
    pageInfoVO.setTotalCount(page.getTotalElements());
    pageInfoVO.setCurrentPage(pageNumber + 1);
    pageInfoVO.setTotalPages(page.getTotalPages());
    pageInfoVO.setData(ArticleUtil.jpaDosToSimpleArticleVOs(articleDOS));
    return pageInfoVO;
}

注意这里的Sort对象的第二个参数不能为gmtUpdate,因为这里我们使用的是SQL语句查询,数据库中并没有gmtUpdate字段,而是gmt_update字段,所以这里应该改为gmt_update,下图为使用gmtUpdate时出现的错误。

使用gmtUpdate时报错

  • 编写Controller
@GetMapping("/getArticlesByQueryString")
public PageInfoVO<SimpleArticleVO> getArticlesByQueryString(
        @RequestParam(name = "queryString") String queryString,
        @RequestParam(name = "pageNumber", required = false, defaultValue = "1") Integer pageNumber) {
    PageInfoVO<SimpleArticleVO> pageInfoVO = articleService.findArticlesByQueryString(queryString, pageNumber);
    return pageInfoVO;
}
正则替换

在上面的ArticleService类中使用了正则替换对文章标题进行操作,代码如下:

String newTitle = title.replaceAll(regex, "<font style='color:red;'>$0</font>");

这句代码实现的功能是将标题中的所有关键词替换为带有红色样式的关键词,下面主要解释一下java中的replaceAll()函数的具体使用方法

  • 首先说一下普通的正则替换,比如将下面字符串中的我们替换为**,我们可以这样写:
public class TestString {
    public static void main(String[] args) {
        String s1 = "我们都是好孩子,他们绝不会在代码里下毒";
        String s2 = s1.replaceAll("我们", "**");
        System.out.println(s2);
    }
}

运行结果如下:
普通替换

  • 如果我们想要替换我们或者他们**,可以这样实现:
public class TestString {
    public static void main(String[] args) {
        String s1 = "我们都是好孩子,他们绝不会在代码里下毒";
        String s2 = s1.replaceAll("我们|他们", "**");
        System.out.println(s2);
    }
}

运行结果如下:
运行结果

  • 假如我们替换的的时候需要用到匹配到的字符串,例如我们想把我们或者他们作为关键词加粗,在关键词两边分别加上**,就可以用如下代码实现:
public class TestString {
    public static void main(String[] args) {
        String s1 = "我们都是好孩子,他们绝不会在代码里下毒";
        String s2 = s1.replaceAll("(我|他)(们)", "**$0**");
        System.out.println(s2);
    }
}

运行结果:
运行结果
这里的$0含义如下图所示,$1为第一个括号里匹配到的内容,$2为第二个括号里匹配到的内容,$0为整体匹配到的内容
正则表达式分组

前端实现
  • /src/request/api/url.js中添加根据含有关键字的字符串查询的URL
const getArticlesByQueryStringUrl = baseUrl + 'api/comm/article/getArticlesByQueryString';
  • /src/request/api/article.js里面封装根据含有关键字字符串查询的方法
getArticlesByQueryString(queryString, pageNumber, callback) {
  axios
    .get(url.getArticlesByQueryStringUrl, {
      params: {
        queryString: queryString,
        pageNumber: pageNumber
      }
    })
    .then(callback)
    .catch(err => {
      console.log("getArticlesByQueryString Error");
    });
},
getArticles(callback) {
  let pageInfo = store.state.articles;
  if (pageInfo.condition) {
    switch (pageInfo.condition) {
      case 'all':
        this.getPageArticles(pageInfo.currentPage, callback);
        break;
      case 'tag':
        // 根据tag查询
        break;
      case 'category':
        // 根据分类查询
        break;
      case 'queryString':
        this.getArticlesByQueryString(pageInfo.queryString, pageInfo.currentPage, callback);
        break;
    }
  }
}
  • navBar.vue中定义点击搜索键之后的回调函数search()和点击博客名时的回调函数toIndex()
methods: {
  ...mapMutations([
    "setIsLogin",
    "setPageInfo",
    "setQueryString",
    "setCurrentPage",
    "setCondition"
  ]),
  // 退出登录
  exit() {
    window.localStorage.removeItem("token");
    this.setIsLogin(false);
  },
  search() {
    if (this.queryString != null && this.queryString != "") {
      this.setCondition({
        condition: "queryString",
        info: this.queryString
      });
      this.setCurrentPage(1);
      this.$api.article.getArticles(resp => {
        this.setPageInfo(resp.data);
      });
    }
  },
  toIndex() {
    this.queryString = "";
    this.setCondition({
      condition: "all"
    });
    this.setCurrentPage(1);
    this.$api.article.getArticles(resp => {
      this.setPageInfo(resp.data);
    });
    router.push("/");
  }
}
  • 在指定元素上绑定回调函数,这里注意不要将b-button的属性设置为submit,否则会提交请求并刷新页面
    函数绑定

需要注意的是:在articles.vue中,渲染标题时应该用v-html才能显示关键字为红色的效果

渲染标题

运行项目效果如下

项目运行效果

  • 1
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值