使用springboot整合elasticsearch+Vue实现数据查询

6 篇文章 0 订阅
2 篇文章 0 订阅

使用elasticsearch做数据服务查询:

实现springboot整合elasticsearchhe和vue做数据查询
注意:在进行此开发的时候一定要保证elasticsearch的版本与springboot的版本对应,否则可能会无法连接,这里用的elasticsearch和springboot的版本分别是5.6.16和2.2.1
elasticsearch仅支持1.8及1.8以上的jdk版本

开发工具

  • 后台开发:Idea;
  • 后台测试:Postman;
  • 前端开发:VScode;

elasticsearch服务搭建(Windows环境):

1、下载安装elasticsearch:官网地址
2、运行elasticsearch:

下载完解压即可:直接双击/bin/ElasticSearch.bat
在这里插入图片描述
一般来说,出现start即为启动成功。

3、浏览器输入:http://127.0.0.1:9200 查看效果:

在这里插入图片描述

4、安装node.js
5、安装grunt-cli:

安装node.js的目录下,输入栏中输入cmd即可进入cmd命令操作符界面;
输入 npm install -g grunt-cli;
输入 grunt -version查看版本;
如果此时报错“grunt不是内部或外部命令,也不是可运行的程序 或批处理文件”,

解决方案:

①进入C:\Users\Administrator\AppData\Roaming\npm,将此路径添加到环境变量PATH中。然后重新打开输入,依旧报错请执行第②步

②进入nodejs安装目录,将如下文件复制到C:\Users\Administrator\AppData\Roaming\npm;然后进入nodejs的node_global目录,将grunt-cli和npm文件夹整体复制到C:\Program Files\nodejs\node_global\node_modules中。

6、安装elasticsearch-head:

安装:https://blog.csdn.net/mjlfto/article/details/79772848

另外一种方法,可通过谷歌的网上应用店进行下载安装elasticsearch-head到扩展程序即可(翻墙问题请自行解决)。

7、查看:

浏览器中输入:http://localhost:9100 查看,谷歌浏览器安装直接双击该扩展程序即可。
在这里插入图片描述

8、下载logstash:

网址https://www.elastic.co/cn/downloads/logstash;
解压,进入解压目录的lib\jars文件夹,放入数据库连接jar包,进入解压目录下bin文件夹下,创建jdbc.sql,logstash.conf文件。
注意:解压目录不能出现中文。
jdbc.sql 文件放入自己所需的查询SQL语句。
logstash.conf文件输入以下内容:

input {
    stdin {
    
    }
    jdbc {
       # mysql 数据库链接
      jdbc_connection_string => "jdbc:mysql://127.0.0.1:3306/vue?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC"
      # 用户名和密码
      jdbc_user => "root"
      jdbc_password => "root"
      # 驱动 修改为mysql连接包位置
      jdbc_driver_library => "D:\DevelopSoftware\logstash-7.4.2\logstash-7.4.2\logstash-core\lib\jars\mysql-connector-java-5.1.37-bin.jar"
      # 驱动类名
      jdbc_driver_class => "com.mysql.jdbc.Driver"
      jdbc_paging_enabled => "true"
      jdbc_page_size => "50000"
#你的SQL的位置,当然,你的SQL也可以直接写在这里。
      #statement => select * from aaa
      # 执行的sql 文件路径+名称 
      statement_filepath => "jdbc.sql"
      # 设置监听间隔  各字段含义(由左至右)分、时、天、月、年,全部为*默认含义为每分钟都更新
      schedule => "* * * * *"
    }
}
output {
    elasticsearch {
        # ES的IP地址及端口
        hosts => ["localhost:9200"]
        # 索引名称 可自定义
        index => "test02"
        # 需要关联的数据库中有有一个id字段,对应类型中的id
        document_id => "%{cid}"
        document_type => "test02"
    }
    stdout {
        # JSON格式输出
        codec => json_lines
    }
}

在解压的目录的bin目录下运行cmd输入 logstash -f logstash.conf。
注意:此工具不能停止运行,一旦停止运行,则无法实时同步数据库的数据!

9、elasticsearch-head连接elasticsearch服务端口进行数据查询:

在这里插入图片描述
在这里插入图片描述
elasticsearch服务器搭建完成。

后台开发:

如何搭建springboot项目请参考此博文,这里不再详细叙述:https://blog.csdn.net/weixin_43303530/article/details/103059004
以下是该项目的依赖及配置:
<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>
server:
  port: 8888
spring:
  data:
    elasticsearch:
      cluster-name: elasticsearch
      cluster-nodes: 127.0.0.1:9300
    #\u4E0B\u9762\u4E00\u9879\u7684\u914D\u7F6E\u7528\u4E8E\u8FD4\u56DE\u4FE1\u606F\u65F6
  jackson:
    default-property-inclusion: non_null
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/vue?useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    jpa:
      hibernate:
        ddl-auto: update  # \u7B2C\u4E00\u6B21\u5EFA\u8868create  \u540E\u9762\u7528update\uFF0C\u8981\u4E0D\u7136\u6BCF\u6B21\u91CD\u542F\u90FD\u4F1A\u65B0\u5EFA\u8868
      show-sql: true
编写实体类:
package com.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 = "test02", type = "test02", shards = 1, replicas = 0)
public class Goods {

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

@Document的作用
分页控制:

package com.elasticsearch.pojo;

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;
    }
}

数据库对应实体:

package com.elasticsearch.pojo;

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

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;
}

dao编写:
package com.elasticsearch.repository;

import com.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编写:

可省略service接口,且结构较为简单,这里不再贴出service的接口代码。

package com.elasticsearch.service.impl;

import com.elasticsearch.pojo.Goods;
import com.elasticsearch.pojo.SearchRequest;
import com.elasticsearch.pojo.SearchResult;
import com.elasticsearch.pojo.XcGoods;
import com.elasticsearch.repository.GoodsRepository;
import com.elasticsearch.service.SearchService;
import com.elasticsearch.utils.PageResult;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
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 org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

@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 SearchResult(total, totalPages, goodsList);
    }
}

controller层编写:
package com.elasticsearch.web;

import com.elasticsearch.pojo.Goods;
import com.elasticsearch.pojo.SearchRequest;
import com.elasticsearch.service.SearchService;
import com.elasticsearch.utils.PageResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

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

工具类:
package com.elasticsearch.utils;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@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;
    }
}
跨域问题:

在config包下创建该配置文件,文件名可自行更改:

package com.elasticsearch.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
public class CorsConfig {
    // 初始化 CorsConfiguration 对象并设置允许的域名、请求头部信息和请求方式
    private CorsConfiguration buildConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*"); // 1 允许任何域名使用
        corsConfiguration.addAllowedHeader("*"); // 2 允许任何头
        corsConfiguration.addAllowedMethod("*"); // 3 允许任何方法(post、get 等)
        return corsConfiguration;
    }
    /**
     * 创建 CorsFilter 对象
     * @return CorsFilter
     */
    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new
                UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", buildConfig()); //拦截所有请求
        return new CorsFilter(source);
    }
}

项目结构:
在这里插入图片描述

测试:

使用Postman进行测试

Postman下载使用
在这里插入图片描述

前台开发:

在进行前台开发前,一定要确认已安装了node.js和Vue-cli
webpack+vue2.0+nodeJs搭建环境

现在,我们进入前台页面的开发,这里我使用的前台开发软件为VScode,使用的前端框架为Vue

1、搭建Vue-cli脚手架并创建一个基于 webpack 模板的新项目:

项目创建:打开终端(快捷键 Ctrl+~),输入命令行
vue init webpack projectName,projectName 为你的项目名称

2、启动项目确保项目初始化没有问题
3、安装axios 插件和安装 Element 插件 :

安装axios:npm install --save axios vue-axios

4、前端页面编写:
<template>
  <div style="width:600px">
    <el-autocomplete
      style="width: 80%"
      class="inline-input"
      v-model="state"
      :fetch-suggestions="querySearch_front"
      placeholder="请输入内容"
      @select="handleSelect"
      :trigger-on-focus="false"
    ></el-autocomplete>
    <el-button @click="find">查找</el-button>
    <div v-show="flag" style="text-align: left;margin-left: 20px;">
      <p v-for="item in tableData" style>
        <a href="#" style="margin-left: 10px;">{{item.name}}</a>
      </p>
      <el-pagination
        style="margin-left: 10px;"
        background
        layout="prev, pager, next"
        :total="total"
        @current-change="handleCurrentChange"
        :current-page="page"
      ></el-pagination>
    </div>
  </div>
</template>

<script>
const axios = require('axios')
export default {
  data () {
    return {
      flag: false,
      total: 0,
      state: '',
      tableData: [],
      restaurants: [],
      options: [],
      page: 1,
      loading: false
    }
  },
  watch: {
    state: { // 监视字段,页数
      handler () {
        if (this.state.length > 0) {
          this.restaurants = []
          this.tableData = []
          this.loadAll()
          // this.flag = false
        } else {
          this.restaurants = []
          this.tableData = []
          this.page = 1
          this.flag = false
          this.total = 0
        }
      }
    },
    page: { // 监视字段,页数
      handler () {
        this.loadAll()
      }
    }
  },
  methods: {
    handleSelect () {
      this.flag = true
      // console.log('快乐' + item)
    },
    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(resp => {
        // handle success
        var rs = resp.data.items
        this.tableData = resp.data.items
        this.total = resp.data.total
        // alert(this.total)
        // this.totalPage = resp.data.totalPage
        // alert(resp.data.totalPage)
        //  alert(rs.length)
        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) {
          // handle error
          console.log(error)
        })
    },
    querySearch_front (queryString, cb) {
      var restaurants = this.restaurants
      // var results = queryString ? restaurants.filter(this.createStateFilter(queryString)) : restaurants
      var results = restaurants
      clearTimeout(this.timeout)
      this.timeout = setTimeout(() => {
        cb(results)
      }, 1000 * Math.random())
    },
    createStateFilter (queryString) {
      return (state) => {
        return (state.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0)
      }
    },
    find () {
      // alert(this.total)
      if (this.total > 0) {
        this.flag = true
      } else {
        this.flag = false
      }
    }
  }
}
</script>

尚未解决:该页面中使用的p标签报错,但不影响运行
在这里插入图片描述
运行页面:
在这里插入图片描述
在这里插入图片描述

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值