使用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进行测试
前台开发:
在进行前台开发前,一定要确认已安装了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标签报错,但不影响运行
运行页面: