springboot+elasticsearch实现一个搜索引擎的功能

springboot+elasticsearch实现一个搜索引擎的功能

一、elasticsearch的安装

ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。(如何安装elasticsearch?

二、解决ES跨域

可以将ES看作是一个数据库,但是通过网络协议连接他时会出现跨域问题,所以需要在启动ES服务之前设置跨域的一些协议,开放自己端口协议等,以免浏览器跨域请求被拦截,在elasticsearch-5.6.16\config\elasticsearch.yml文件中配置如下代码即可。

http.cors.enabled: true
http.cors.allow-origin: "*"

三、elasticsearch-head下载及安装

1、谷歌浏览器可以直接在Chrome商城中安装elasticsearch-head插件

2、其他浏览器安装elasticsearch-head插件

四、完成springboot 和 elasticsearch的整合

1、创建spring boot项目,加载pom.xml。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.demo</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>elasticsearch</name>
    <description>使用springboot+elasticsearch做一个搜索引擎</description>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <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>
        </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>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    <repositories>
        <repository>
            <id>aliyunmaven</id>
            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
        </repository>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

</project>

2、application.yml配置

server:
  port: 8888
spring:
  data:
    elasticsearch:
      cluster-name: elasticsearch
      cluster-nodes: 127.0.0.1:9300
  jackson:
    default-property-inclusion: non_null
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/elasticsearch?useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: sasa
    driver-class-name: com.mysql.cj.jdbc.Driver
    jpa:
      hibernate:
        ddl-auto: update  
      show-sql: true

五、实体类

先说明一下,测试数据来源与商品分类信息,数据存放在mysql数据库,我们需要将mysql的数据全部查询出来然后存放到elasticsearch中,这儿用的springdata来方便我们连接mysql和elastic

1、对应mysql 实体类

package com.demo.elasticsearch.pojo;

import lombok.Data;

import javax.persistence.*;
import java.io.Serializable;

@Data
@Entity(name="wp_ex_source_goods_tb_cat_copy")
public class XcGoods implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cid")
    private Long cid;
    @Column(name = "name")
    private String name;
    @Column(name = "is_parent")
    private String isParent;
    @Column(name = "parent_id")
    private String parentId;
    @Column(name = "level")
    private Long level;
    @Column(name = "pathid")
    private String pathid;
    @Column(name = "path")
    private String path;
}

2、对应es实体类

package com.demo.elasticsearch.pojo;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;


@Data
@Document(indexName = "goodscat", type = "docs", shards = 1, replicas = 0)
public class Goods {

    @Id
    private Long cid;
//    @Field(type = FieldType.Keyword, analyzer = "ik_max_word")
//    private String all; // 所有需要被搜索的信息,包含标题,分类,甚至品牌
    @Field(type = FieldType.Keyword, index = true, analyzer = "ik_max_word")
    private String name;
    private String isParent;
    private String parentId;
    private Long level;
    private String pathid;
}

3、分页工具类

package com.demo.elasticsearch.pojo;


public class SearchRequest {
    private String key;// 搜索条件

    private Integer page;// 当前页

    private static final Integer DEFAULT_SIZE = 20;// 每页大小,不从页面接收,而是固定大小
    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;
    }
}

六、dao层

1、对应mysql

package com.demo.elasticsearch.repository;

import com.demo.elasticsearch.pojo.Goods;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

public interface GoodsRepository extends ElasticsearchRepository<Goods,Long> {

}

2、对应es

package com.demo.elasticsearch.repository;

import com.demo.elasticsearch.pojo.XcGoods;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

public interface XcGoodsRepository extends JpaRepository<XcGoods,Long>, JpaSpecificationExecutor<XcGoods> {

}

七、service层

1、service

package com.demo.elasticsearch.service;


import com.demo.elasticsearch.pojo.Goods;
import com.demo.elasticsearch.pojo.SearchRequest;
import com.demo.elasticsearch.pojo.XcGoods;
import com.demo.elasticsearch.utils.PageResult;

public interface SearchService {

    Goods buildGoods(XcGoods goods);

    PageResult<Goods> search(SearchRequest request);

}

2、impl

package com.demo.elasticsearch.service.impl;

import com.demo.elasticsearch.pojo.Goods;
import com.demo.elasticsearch.pojo.SearchRequest;
import com.demo.elasticsearch.pojo.XcGoods;
import com.demo.elasticsearch.repository.GoodsRepository;
import com.demo.elasticsearch.service.SearchService;
import com.demo.elasticsearch.utils.PageResult;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.*;

@Slf4j
@Service
public class SearchServiceImpl implements SearchService {


    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private ElasticsearchTemplate template;

    @Override
    public Goods buildGoods(XcGoods xcgoods) {
        //搜索字段
//        String all =  xcgoods.getName();

        //构建goods对象
        Goods goods = new Goods();
        goods.setCid(xcgoods.getCid());
        goods.setName(xcgoods.getName());
        goods.setIsParent(xcgoods.getIsParent());
        goods.setParentId(xcgoods.getParentId());
        goods.setPathid(xcgoods.getPathid());
        goods.setLevel(xcgoods.getLevel());
        // 搜索字段,包含标题,分类,品牌,规格,等等
//        goods.setAll(all);

        return goods;
    }


    @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()));

        //查询
        //Page<Goods> result = goodsRepository.search(queryBuilder.build());
        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);
    }


}

3、测试类及操作解释

package com.demo.elasticsearch.service.impl;

import com.demo.elasticsearch.pojo.Goods;
import com.demo.elasticsearch.pojo.XcGoods;
import com.demo.elasticsearch.repository.GoodsRepository;
import com.demo.elasticsearch.repository.XcGoodsRepository;
import com.demo.elasticsearch.service.SearchService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.stream.Collectors;

@SpringBootTest
public class SearchServiceImplTest {

    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private ElasticsearchTemplate template;

    @Autowired
    private SearchService searchService;


    @Autowired
    private XcGoodsRepository xcGoodsRepository;

    @Test
    public void testCreateIndex() {

        template.createIndex(Goods.class);
        template.putMapping(Goods.class);
    }

    @Test
    public void loadData() {
        int page = 1;
        int rows = 100;
        int size = 0;

        //查询spu信息
        do {
            Page<XcGoods> result = xcGoodsRepository.findAll(PageRequest.of(page - 1, rows));
            List<XcGoods> spuList = result.getContent();
            if (CollectionUtils.isEmpty(spuList)){
                break;
            }
            //构建成goods
            List<Goods> goodsList = spuList.stream()
                    .map(searchService::buildGoods).collect(Collectors.toList());
            //存入索引库
            goodsRepository.saveAll(goodsList);
            //翻页
            page++;
            size = spuList.size();
        } while (size == 100);

    }
}

最终样式

在这里插入图片描述

八、前端VUE+ELEMENT

1、创建vue项目(如何创建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、创建vue文件,编写代码

<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>
        搜索
        <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>
<script>
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++) {
            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>
<style>
.el-autocomplete {
  width: 400px;
}
.p2 {
  margin-left: 160px;
  text-align: left;
  font-size: 20px;
}
a {
  color: blue;
}
.div2 {
  /* background: blue; */
  margin-top: 25px;
  padding-top: 25px;
  margin-left: 270px;
  width: 750px;
  height: 650px;
  /* border: 1px solid #b0c4de; */
}
</style>

最终样式(因为是自己编写的vue页面,不够完善,运行会报一个小bug,不会影响,直接在浏览器访问即可)

在这里插入图片描述

  • 3
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值