SpringBoot+elasticsearch实现全文检索,做一个简单的搜索引擎

一、什么是elasticsearch

答:“ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。我们建立一个网站或应用程序,并要添加搜索功能,但是想要完成搜索工作的创建是非常困难的。我们希望搜索解决方案要运行速度快,我们希望能有一个零配置和一个完全免费的搜索模式,我们希望能够简单地使用JSON通过HTTP来索引数据,我们希望我们的搜索服务器始终可用,我们希望能够从一台开始并扩展到数百台,我们要实时搜索,我们要简单的多租户,我们希望建立一个云的解决方案。因此我们利用Elasticsearch来解决所有这些问题及可能出现的更多其它问题。”

二、配置Elasticsearch和logstash

https://blog.csdn.net/fjyab/article/details/81101284
https://blog.csdn.net/wzn1054162229/article/details/101480921
注:上方博客可能配置方式不同,请自行参照。其中配置logstash将数据库数据导入到Elasticsearch中时,如果运行不能连接,那么数据库连接JAR包请放到下方路径下:在这里插入图片描述

三、后台代码编写

1、创建SpringBoot项目,通过spring boot操作elasticsearch需要通过spring data elasticsearch来实现

2、导入依赖,和yml配置

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- elasticsearch -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
server:
  port: 8888
spring:
  data:
    elasticsearch:
      cluster-name: es
      cluster-nodes: 127.0.0.1:9300
  jackson:
    default-property-inclusion: non_null

3、编写实体类:Goods .java


@Data
@Document(indexName = "goodscat", type = "docs", shards = 1, replicas = 0)//选中elasticsearch索引
public class Goods {

    private Long cid;
    private String name;
    private String isParent;
    private String parentId;
    private Long level;
    private String pathid;
}

4、编写repository层,创建GoodsRepository.java,继承ElasticsearchRepository

public interface GoodsRepository extends ElasticsearchRepository<Goods,Long> {

}

5、编写service和serviceImpl

public interface SearchService {
    PageResult<Goods> search(SearchRequest request);
}
@Slf4j
@Service
public class SearchServiceImpl implements SearchService {
    @Autowired
    private GoodsRepository goodsRepository;
    @Autowired
    private ElasticsearchTemplate template;
    @Override
    public PageResult<Goods> search(SearchRequest request) {
        int page = request.getPage() - 1;
        int size = request.getSize();
        //创建查询构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //结果过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"cid", "name"}, null));
        //分页
        queryBuilder.withPageable(PageRequest.of(page, size));
        //过滤
        queryBuilder.withQuery(QueryBuilders.matchQuery("name", request.getKey()));
        //查询
        AggregatedPage<Goods> result = template.queryForPage(queryBuilder.build(), Goods.class);
        //解析结果
        //分页结果解析
        long total = result.getTotalElements();
        Integer totalPages1 = result.getTotalPages();    //失效
        Long totalPages = total % size == 0 ? total / size : total / size + 1;
        List<Goods> goodsList = result.getContent();
        //解析聚合结果
        return new PageResult(total, totalPages, goodsList);
     }
}

6、编写分页工具类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult<T> {
    private Long total;// 总条数
    private Long totalPage;// 总页数
    private List<T> items;// 当前页数据
    public PageResult(Long total, List<T> items) {
        this.total = total;
        this.items = items;
    }
}

6、编写controller

@RestController
public class SearchController {
    @Autowired
    private SearchService searchService;

    /**
     * 搜索功能
     * @param request
     * @return
     */
    @GetMapping("search")
    public ResponseEntity<PageResult<Goods>> search(SearchRequest request) {
        return ResponseEntity.ok(searchService.search(request));
    }
}

7、编写一个搜索条件接收类,用来接收前台搜索条件,和分页条件

public class SearchRequest {
    private String key;// 搜索条件
    private Integer page;// 当前页
    private static final Integer DEFAULT_SIZE = 10;// 每页大小,不从页面接收,而是固定大小
    private static final Integer DEFAULT_PAGE = 1;// 默认页
    public String getKey() {
        return key;
    }
    public void setKey(String key) {
        this.key = key;
    }
    public Integer getPage() {
        if(page == null){
            return DEFAULT_PAGE;
        }
        // 获取页码时做一些校验,不能小于1
        return Math.max(DEFAULT_PAGE, page);
    }
    public void setPage(Integer page) {
        this.page = page;
    }
    public Integer getSize() {
        return DEFAULT_SIZE;
    }
}

8、返回的json数据格式

{
    "total": 3,
    "totalPage": 1,
    "items": [
        {
            "cid": 50010363,
            "name": "许愿瓶/幸运星瓶"
        },
        {
            "cid": 70000558,
            "name": "英国特许公认会计师ACCA"
        },
        {
            "cid": 50004583,
            "name": "英国特许公认会计师ACCA"
        }
    ]
}

四、Vue+Element前端编写

1、创建vue项目
2、安装 axios 插件,在当前项目下的终端输入命令: npm install --save axios vue-axios
安装 Element 插件,在当前项目下的终端输入命令:npm i element-ui -S
3、在 src 文件夹下的程序入口 main.js 中导入

import axios from 'axios'
import VueAxios from 'vue-axios'
// element-ui 引入文件
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
//注册 VueAxios, axios
Vue.use(VueAxios, axios)
Vue.use(ElementUI)

4、导入template,编写静态页面,并绑定下方方法和属性

<template>
  <div>
    <!-- 上方搜索框,和下拉即时搜索 -->
    <el-autocomplete
      v-model="state"
      :fetch-suggestions="querySearchAsync"
      placeholder="请输入内容"
      @select="handleSelect"
      select-when-unmatched="true"
      :debounce="0"
    ></el-autocomplete>
    <el-button slot="append" icon="el-icon-search" @click="onSubmit"></el-button>

<!-- 下方搜索结果显示 -->
    <div class="div2" v-show="con">
      <p style="font-size:22px">
        搜索
        <span style="color:	#F08080">{{state}}</span>的结果(总共搜索到
        <span style="color:	#F08080">{{total}}</span>条记录)
      </p>
      <p v-for="entity in All" class="p2">
        <a href="http://www.baidu.com">{{entity.name}}</a>
      </p>
      <!-- 分页组件 -->
      <el-pagination
        background
        layout="prev, pager, next"
        :total="total"
        :page-size="15"
        @current-change="handleCurrentChange"
        :current-page="page"
      ></el-pagination>
    </div>
  </div>
</template>

注:其中显示搜索内容的p标签,因为绑定了v-for和element产生了冲突,会报错,但是不影响效果,请忽略

5、编写各部分js代码,定义属性,方法,请求。各方法属性有注释,请认真查看

<script>
//导入axios,进行ajax访问
const axios = require("axios");
export default {
  data () {
    return {
      con: false,//控制下方显示框,隐藏或显示
      restaurants: [],//即时搜索,下拉框信息
      state: '',//搜索条件
      timeout: null,
      All: [],//下方显示框数据集合
      total: 0,  //搜索返回的总结果数量
      page: 1,//分页的当前页数
    };
  },
  watch: {
    state: { // 监视字段,页数
      handler () {
        if (this.state.length > 0) {//如果有搜索条件
          this.restaurants = [];//先清空即时搜索集合数据
          this.loadAll();//查询
        } else {
          this.con = false;//没有搜索条件,隐藏下方数据显示窗口
          this.restaurants = [];
          this.All = [];//并清空结果集合
          this.page = 1;//分页归一
        }
      }
    },
    page: { // 监视字段,页数
      handler () {
        this.loadAll();//如果页面发生变化,就查询新的页面的数据
      }
    }
  },
  methods: {
    //分页是上下页,触发的方法
    handleCurrentChange (val) {//当前页
      this.page = val;
      console.log(`当前页: ${val}`);
    },

    loadAll () {
      var app = this;
      axios.get("http://localhost:8888/search", {
        params: {
          'key': app.state,//搜索条件
          'page': app.page//当前第几页
        }
      }).then(function (resp) {
        app.total = resp.data.total;//当前数据一共有多少条
        var rs = resp.data.items;
        app.All = rs;//给显示结果的集合赋值
        if (rs.length > 0) {
          for (var i = 0; i < 10; i++) {//只显示10条提示
            app.restaurants[i] = { value: rs[i].name, cid: rs[i].cid }//给及时搜索下拉框赋值
          }
        }
      }).catch(function (error) {
        console.log(error);
      });
    },
    querySearchAsync (queryString, cb) {//加载即时搜索条件
      var results = this.restaurants;
      clearTimeout(this.timeout);
      this.timeout = setTimeout(() => {
        cb(results);
      }, 1000 * Math.random());
    },
    handleSelect (item) {//选中下拉提示的数据时触发
      if (this.All != "") {
        this.con = true;//显示结果
      }
    },
    onSubmit () {
      if (this.All != "") {//点击查询图标时,显示结果
        this.con = true;
      }
    }
  },
};
</script>

6、编写style样式

<style>
.el-autocomplete {
  width: 400px;
}
.p2 {
  margin-left: 160px;
  text-align: left;
  font-size: 20px;
}
a {
  color: #4f5a75;
}
.div2 {
  /* background: blue; */
  margin-top: 25px;
  padding-top: 25px;
  margin-left: 270px;
  width: 750px;
  height: 600px;
  border: 1px solid #b0c4de;
}
</style>

五、演示效果
1、即时搜索
在这里插入图片描述
2、搜索结果分页显示
在这里插入图片描述

六、项目dome源码和sql文件

链接:https://pan.baidu.com/s/190d0nQhFQ-ZIS1dcAqdNig
提取码:tbp5

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐