搜索和数据分析引擎--Elasticsearch

Elasticsearch 安装

docker安装elasticsearch

docker镜像下载

docker pull elasticsearch:5.6.8

安装es容器

docker run -di --name=jwang_elasticsearch -p 9200:9200 -p 9300:9300 elasticsearch:5.6.8
  • 9200端口(Web管理平台端口) 9300(服务默认端口)
浏览器输入地址访问:"http://192.168.211.132:9200/"

1559425749415

开启远程连接

上面完成安装后,es并不能正常使用,elasticsearch从5版本以后默认不开启远程连接,程序直接连接会报如下错误:

failed to load elasticsearch nodes : org.elasticsearch.client.transport.NoNodeAvailableException: None of the configured nodes are available: [{#transport#-1}{5ttLpMhkRjKLkvoY7ltUWg}{192.168.211.132}{192.168.211.132:9300}]

我们需要修改es配置开启远程链接:

copy elasticsearch.yml文件到宿主机,修改之后在copy回去

docker cp jwang_elasticsearch:/usr/share/elasticsearch/config/elasticsearch.yml ./

修改elasticsearch.yml文件 vim elasticsearch.yml

1609080027289

copy回docker容器中:

docker cp ./elasticsearch.yml jwang_elasticsearch:/usr/share/elasticsearch/config/

系统参数配置

重启后发现重启启动失败了,这时什么原因呢?这与我们刚才修改的配置有关,因为elasticsearch在启动的时候会进行一些检查,比如最多打开的文件的个数以及虚拟内存区域数量等等,如果你放开了此配置,意味着需要打开更多的文件以及虚拟内存,所以我们还需要系统调优

修改vi /etc/security/limits.conf ,追加内容 (nofile是单个进程允许打开的最大文件个数 soft nofile 是软限制 hard nofile是硬限制 )

* soft nofile 65536
* hard nofile 65536

修改vi /etc/sysctl.conf,追加内容 (限制一个进程可以拥有的VMA(虚拟内存区域)的数量 )

vm.max_map_count=655360

执行下面命令 修改内核参数马上生效

sysctl -p

重新启动虚拟机,再次启动容器,发现已经可以启动并远程访问

reboot

创建es-head插件:

docker pull docker.io/mobz/elasticsearch-head:5

docker run -di --name=es-head -p 9100:9100 docker.io/mobz/elasticsearch-head:5

跨域配置

修改elasticsearch/config下的配置文件:elasticsearch.yml,增加以下三句命令,并重启:

http.cors.enabled: true
http.cors.allow-origin: "*"
network.host: 192.168.211.132

其中:
http.cors.enabled: true:此步为允许elasticsearch跨域访问,默认是false。
http.cors.allow-origin: ““:表示跨域访问允许的域名地址(表示任意)。

重启

docker restart jwang_elasticsearch

小提示:如果想让容器开启重启,可以执行下面命令

docker update --restart=always 容器名称或者容器id

1584928112187

IK分词器安装

安装ik分词器

IK分词器下载地址https://github.com/medcl/elasticsearch-analysis-ik/releases

将ik分词器上传到服务器上,然后解压,并改名字为ik

unzip elasticsearch-analysis-ik-5.6.8.zip
mv elasticsearch ik

将ik目录拷贝到docker容器的plugins目录下

docker cp ./ik jwang_elasticsearch:/usr/share/elasticsearch/plugins

重启容器:

docker restart jwang_elasticsearch

IK分词器测试

访问:`http://192.168.211.132:9200/_analyze?analyzer=ik_smart&pretty=true&text=我是程序员`

1559427846075

访问:`http://192.168.211.132:9200/_analyze?analyzer=ik_max_word&pretty=true&text=我是程序员`

1559427892947

Kibana使用-掌握DSL语句

我们上面使用的是elasticsearch-head插件实现数据查找的,但是elasticsearch-head的功能比较单一,我们这里需要一个更专业的工具实现对日志的实时分析,也就是我们接下来要讲的kibana。

Kibana 是一款开源的数据分析和可视化平台,它是 Elastic Stack 成员之一,设计用于和 Elasticsearch 协作。您可以使用 Kibana 对 Elasticsearch 索引中的数据进行搜索、查看、交互操作。您可以很方便的利用图表、表格及地图对数据进行多元化的分析和呈现。

Kibana 可以使大数据通俗易懂。它很简单,基于浏览器的界面便于您快速创建和分享动态数据仪表板来追踪 Elasticsearch 的实时数据变化。

搭建 Kibana 非常简单。您可以分分钟完成 Kibana 的安装并开始探索 Elasticsearch 的索引数据 — 没有代码、不需要额外的基础设施。

Kibana下载安装

我们项目中不再使用linux,直接使用Docker,所有这里就不演示在windows的下载安装了。

(1)镜像下载

docker pull docker.io/kibana:5.6.8

(2)安装kibana容器

执行如下命令,开始安装kibana容器

docker run -it -d -e ELASTICSEARCH_URL=http://192.168.211.132:9200 --name kibana --restart=always -p 5601:5601 kibana:5.6.8



ELASTICSEARCH_URL=http://192.168.211.132:9200:是指链接的ES地址

restart=always:每次服务都会重启,也就是开启启动

5601:5601:端口号

(3)访问测试

访问http://192.168.211.132:5601如下:

1559533771948

Kibana使用

配置索引

要使用Kibana,您必须至少配置一个索引。索引用于标识Elasticsearch索引以运行搜索和分析。它们还用于配置字段。

1554423078755

我们修改索引名称的匹配方式即可,下面2个选项不用勾选。点击create,会展示出当前配置的索引的域信息,如下图:

1554423578891

域的每个标题选项分别代表如下意思:

1554423779455

数据搜索

Discover为数据搜索部分,可以对日志信息进行搜索操作。

1554501163624

可以使用Discover实现数据搜索过滤和搜索条件显示以及关键词搜索,如下图:

1554501381459

DSL语句使用

Query DSL结构化查询介绍

Query DSL是一个Java开源框架用于构建类型安全的SQL查询语句。采用API代替传统的拼接字符串来构造查询语句。目前Querydsl支持的平台包括JPA,JDO,SQL,Java Collections,RDF,Lucene,Hibernate Search。elasticsearch提供了一整套基于JSON的查询DSL语言来定义查询。
Query DSL当作是一系列的抽象的查询表达式树(AST)特定查询能够包含其它的查询,(如 bool ), 有些查询能够包含过滤器(如 constant_score), 还有的可以同时包含查询和过滤器 (如 filtered). 都能够从ES支持查询集合里面选择任意一个查询或者是从过滤器集合里面挑选出任意一个过滤器, 这样的话,我们就可以构造出任意复杂(maybe 非常有趣)的查询了。

索引操作

(1)查询所有索引

GET /_cat/indices?v

结果如下:

1564603562118

(2)删除某个索引

DELETE /skuinfo

效果如下:

1564603680699

(3)新增索引

PUT /user

效果如下:

1564603974567

(4)创建映射

PUT /user/userinfo/_mapping
{
"properties": {
"name":{
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart",
"store": false
},
"city":{
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart",
"store": false
},
"age":{
"type": "long",
"store": false
},
"description":{
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart",
"store": false
}
}
}

效果如下:

1564604795013

(5)新增文档数据

PUT /user/userinfo/1
{
"name":"李四",
"age":22,
"city":"深圳",
"description":"李四来自湖北武汉!"
}

效果如下:

1564604217330

(6)修改数据

a.替换操作

更新数据可以使用之前的增加操作,这种操作会将整个数据替换掉,代码如下:

#更新数据,id=4
PUT /user/userinfo/4
{
"name":"张三丰",
"description":"在武汉读书,家在武汉!在深圳工作!"
}

效果如下:

1564606854935

使用GET命令查看:

#根据ID查询
GET /user/userinfo/4

b.更新操作

我们先使用下面命令恢复数据:

#恢复文档数据 id=4
PUT /user/userinfo/4
{
"name":"张三丰",
"age":66,
"city":"武汉",
"description":"在武汉读书,家在武汉!"
}

使用POST更新某个列的数据

#使用POST更新某个域的数据
POST /user/userinfo/4/_update
{
"doc":{
"name":"张三丰",
"description":"在武汉读书,家在武汉!在深圳工作!"
}
}

效果如下:

1564607209527

使用GET命令查看:

#根据ID查询
GET /user/userinfo/4

(7)删除Document

#删除数据
DELETE user/userinfo/7
数据查询

(1)查询所有数据

#查询所有
GET /user/_search

效果如下:

1564605593912

(2)根据ID查询

#根据ID查询
GET /user/userinfo/2

效果如下:

1564605676871

(3)Sort排序

#搜索排序
GET /user/_search
{
"query":{
"match_all": {}
},
"sort":{
"age":{
"order":"desc"
}
}
}

效果如下:

1564605923564

(4)分页

#分页实现
GET /user/_search
{
"query":{
"match_all": {}
},
"sort":{
"age":{
"order":"desc"
}
},
"from": 0,
"size": 2
}

解释:

from:从下N的记录开始查询

size:每页显示条数

效果如下:

1564606466826

过滤查询

(1)term过滤

term主要用于分词精确匹配,如字符串、数值、日期等(不适合情况:1.列中除英文字符外有其它值 2.字符串值中有冒号或中文 3.系统自带属性如_version)

如下案例:

#过滤查询-term
GET _search
{
"query":{
"term":{
"city":"武汉"
}
}
}

效果如下:

1564607758341

(2)terms 过滤

terms 跟 term 有点类似,但 terms 允许指定多个匹配条件。 如果某个字段指定了多个值,那么文档需要一起去做匹配 。

案例如下:

#过滤查询-terms 允许多个Term
GET _search
{
"query":{
"terms":{
"city":
[
"武汉",
"广州"
]
}
}
}

果如下:

1564608161056

(3) range 过滤

range过滤允许我们按照指定范围查找一批数据。例如我们查询年龄范围

案例如下:

#过滤-range 范围过滤
#gt表示> gte表示=>
#lt表示< lte表示<=
GET _search
{
"query":{
"range": {
"age": {
"gte": 30,
"lte": 57
}
}
}
}

上图效果如下:

1564608377202

(4)exists过滤

exists 过滤可以用于查找拥有某个域的数据

案例如下:

#过滤搜索 exists:是指包含某个域的数据检索
GET _search
{
"query": {
"exists":{
"field":"address"
}
}
}

效果如下:

1564608891037

(5) bool 过滤

bool 过滤可以用来合并多个过滤条件查询结果的布尔逻辑,它包含一下操作符:

  • must : 多个查询条件的完全匹配,相当于 and。
  • must_not : 多个查询条件的相反匹配,相当于 not。
  • should : 至少有一个查询条件匹配, 相当于 or。

这些参数可以分别继承一个过滤条件或者一个过滤条件的数组:

案例如下:

#过滤搜索 bool 
#must : 多个查询条件的完全匹配,相当于 and。
#must_not : 多个查询条件的相反匹配,相当于 not。
#should : 至少有一个查询条件匹配, 相当于 or。
GET _search
{
"query": {
"bool": {
"must": [
{
"term": {
"city": {
"value": "深圳"
}
}
},
{
"range":{
"age":{
"gte":20,
"lte":99
}
}
}
]
}
}
}

效果如下:

1564609793695

(6) match_all 查询

可以查询到所有文档,是没有查询条件下的默认语句。

案例如下:

#查询所有 match_all
GET _search
{
"query": {
"match_all": {}
}
}

(7) match 查询

match查询是一个标准查询,不管你需要全文本查询还是精确查询基本上都要用到它。

如果你使用 match 查询一个全文本字段,它会在真正查询之前用分析器先分析match一下查询字符:

案例如下:

#字符串匹配
GET _search
{
"query": {
"match": {
"description": "武汉"
}
}
}

效果如下:

1564609964569

(8)prefix 查询

以什么字符开头的,可以更简单地用 prefix ,例如查询所有以张开始的用户描述

案例如下:

#前缀匹配 prefix
GET _search
{
"query": {
"prefix": {
"name": {
"value": "赵"
}
}
}
}

效果如下:

1564610088455

(9)multi_match 查询

multi_match查询允许你做match查询的基础上同时搜索多个字段,在多个字段中同时查一个

案例如下:

#多个域匹配搜索
GET _search
{
"query": {
"multi_match": {
"query": "深圳",
"fields": [
"city",
"description"
]
}
}
}

效果如下:

1564610272233

高亮查询

执行查询的DSL语句:

GET user/_search
{
"query": {
"match": {
"description": "武汉"
}
},
"highlight": {
"pre_tags": "<span style='color:red'>",
"post_tags": "</span>",
"fields": {
"description": {}
}
}
}

查询结果:

1578022792956

完整DSL语句代码

#查看所有索引
GET /_cat/indices?v

#删除某个索引
DELETE /skuinfo

#新增索引
PUT /user

#创建映射
PUT /user/userinfo/_mapping
{
"properties": {
"name":{
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart",
"store": false
},
"city":{
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart",
"store": false
},
"age":{
"type": "long",
"store": false
},
"description":{
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart",
"store": false
}
}
}

#新增文档数据 id=1
PUT /user/userinfo/1
{
"name":"李四",
"age":22,
"city":"深圳",
"description":"李四来自湖北武汉!"
}

#新增文档数据 id=2
PUT /user/userinfo/2
{
"name":"王五",
"age":35,
"city":"深圳",
"description":"王五家住在深圳!"
}

#新增文档数据 id=3
PUT /user/userinfo/3
{
"name":"张三",
"age":19,
"city":"深圳",
"description":"在深圳打工,来自湖北武汉"
}

#新增文档数据 id=4
PUT /user/userinfo/4
{
"name":"张三丰",
"age":66,
"city":"武汉",
"description":"在武汉读书,家在武汉!"
}

#新增文档数据 id=5
PUT /user/userinfo/5
{
"name":"赵子龙",
"age":77,
"city":"广州",
"description":"赵子龙来自深圳宝安,但是在广州工作!",
"address":"广东省茂名市"
}

#新增文档数据 id=6
PUT /user/userinfo/6
{
"name":"赵毅",
"age":55,
"city":"广州",
"description":"赵毅来自广州白云区,从事电子商务8年!"
}

#新增文档数据 id=7
PUT /user/userinfo/7
{
"name":"赵哈哈",
"age":57,
"city":"武汉",
"description":"武汉赵哈哈,在深圳打工已有半年了,月薪7500!"
}

#更新数据,id=4
PUT /user/userinfo/4
{
"name":"张三丰",
"description":"在武汉读书,家在武汉!在深圳工作!"
}


#根据ID查询
GET /user/userinfo/4

#恢复文档数据 id=4
PUT /user/userinfo/4
{
"name":"张三丰",
"age":66,
"city":"武汉",
"description":"在武汉读书,家在武汉!"
}

#使用POST更新某个域的数据
POST /user/userinfo/4/_update
{
"doc":{
"name":"张三丰",
"description":"在武汉读书,家在武汉!在深圳工作!"
}
}

#根据ID查询
GET /user/userinfo/4

#删除数据
DELETE user/userinfo/4

#查询所有
GET /user/_search

#根据ID查询
GET /user/userinfo/2

#搜索排序
GET /user/_search
{
"query":{
"match_all": {}
},
"sort":{
"age":{
"order":"desc"
}
}
}

#分页实现
GET /user/_search
{
"query":{
"match_all": {}
},
"sort":{
"age":{
"order":"desc"
}
},
"from": 0,
"size": 2
}

#过滤查询-term
GET _search
{
"query":{
"term":{
"city":"武汉"
}
}
}

#过滤查询-terms 允许多个Term
GET _search
{
"query":{
"terms":{
"city":
[
"武汉",
"广州"
]
}
}
}

#过滤-range 范围过滤
#gt表示> gte表示=>
#lt表示< lte表示<=
GET _search
{
"query":{
"range": {
"age": {
"gte": 30,
"lte": 57
}
}
}
}


#过滤搜索 exists:是指包含某个域的数据检索
GET _search
{
"query": {
"exists":{
"field":"address"
}
}
}

#过滤搜索 bool
#must : 多个查询条件的完全匹配,相当于 and。
#must_not : 多个查询条件的相反匹配,相当于 not。
#should : 至少有一个查询条件匹配, 相当于 or。
GET _search
{
"query": {
"bool": {
"must": [
{
"term": {
"city": {
"value": "深圳"
}
}
},
{
"range":{
"age":{
"gte":20,
"lte":99
}
}
}
]
}
}
}

#查询所有 match_all
GET _search
{
"query": {
"match_all": {}
}
}

#字符串匹配
GET _search
{
"query": {
"match": {
"description": "武汉"
}
}
}

#前缀匹配 prefix
GET _search
{
"query": {
"prefix": {
"name": {
"value": "赵"
}
}
}
}

#多个域匹配搜索
GET _search
{
"query": {
"multi_match": {
"query": "深圳",
"fields": [
"city",
"description"
]
}
}
}

数据导入ES

SpringData Elasticsearch介绍

SpringData介绍

Spring Data是一个用于简化数据库访问,并支持云服务的开源框架。其主要目标是使得对数据的访问变得方便快捷,并支持map-reduce框架和云计算数据服务。 Spring Data可以极大的简化JPA的写法,可以在几乎不用写实现的情况下,实现对数据的访问和操作。除了CRUD外,还包括如分页、排序等一些常用的功能。

Spring Data的官网:http://projects.spring.io/spring-data/

SpringData ES介绍

Spring Data ElasticSearch 基于 spring data API 简化 elasticSearch操作,将原始操作elasticSearch的客户端API 进行封装 。Spring Data为Elasticsearch项目提供集成搜索引擎。Spring Data Elasticsearch POJO的关键功能区域为中心的模型与Elastichsearch交互文档和轻松地编写一个存储库数据访问层。 官方网站:http://projects.spring.io/spring-data-elasticsearch/

搜索工程搭建

创建搜索微服务工程,jwang-service-search,该工程主要提供搜索服务以及索引数据的更新操作。

(1)API工程搭建

首先创建search的API工程,在jwang-service-api中创建jwang-service-search-api,如下图:

1560825278495

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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>jwang-service-api</artifactId>
<groupId>com.jwang</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jwang-service-search-api</artifactId>

<dependencies>
<!--goods API依赖-->
<dependency>
<groupId>com.jwang</groupId>
<artifactId>jwang-service-goods-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--SpringDataES依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
</dependencies>
</project>

(2)搜索微服务搭建

在jwang-service中搭建jwang-service-search微服务,并进行相关配置。

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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>jwang-service</artifactId>
<groupId>com.jwang</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jwang-service-search</artifactId>

<dependencies>
<!--依赖search api-->
<dependency>
<groupId>com.jwang</groupId>
<artifactId>jwang-service-search-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>

</project>

application.yml配置

server:
port: 18085
spring:
application:
name: search
data:
elasticsearch:
cluster-name: my-application
cluster-nodes: 192.168.211.132:9300
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:7001/eureka
instance:
prefer-ip-address: true
feign:
hystrix:
enabled: true
#超时配置
ribbon:
ReadTimeout: 300000

hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 10000

配置说明:

connection-timeout:服务连接超时时间
socket-connect:HTTP请求超时时间
ribbon.ReadTimeout: Feign请求读取数据超时时间
timeoutInMilliseconds:feign连接超时时间
cluster-name:Elasticsearch的集群节点名称,这里需要和Elasticsearch集群节点名称保持一致
cluster-nodes:Elasticsearch节点通信地址

(3)启动类

创建com.jwang.SearchApplication作为搜索微服务工程的启动类,代码如下:

@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
@EnableEurekaClient
public class SearchApplication {

public static void main(String[] args) {
/**
* Springboot整合Elasticsearch 在项目启动前设置一下的属性,防止报错
* 解决netty冲突后初始化client时还会抛出异常
* availableProcessors is already set to [12], rejecting [12]
***/
//System.setProperty("es.set.netty.runtime.available.processors", "false");
SpringApplication.run(SearchApplication.class,args);
}
}

分别创建对应的包,dao、service、controller,如下图:

1560825119409

数据导入

现在需要将数据从数据库中查询出来,然后将数据导入到ES中。

1609080628068

数据导入流程如下:

1.先将数据从数据库表中查询出来,将其导入到es服务器中
1.1 商品微服务查询出商品sku表的数据
1.2 搜索微服务通过feign调用商品微服务 获取到数据集(sku的列表数据)
1.3 通过spring data elasticsearch api 将其导入到es服务器中(过程就是建立倒排索引结构,存储数据到文档区域中)
2.实现搜索功能

文档映射Bean创建

搜索商品的时候,会根据如下属性搜索数据,并且不是所有的属性都需要分词搜索,我们创建JavaBean,将JavaBean数据存入到ES中要以搜索条件和搜索展示结果为依据,部分关键搜索条件分析如下:

1.可能会根据商品名称搜素,而且可以搜索商品名称中的任意一个词语,所以需要分词
2.可能会根据商品分类搜索,商品分类不需要分词
3.可能会根据商品品牌搜索,商品品牌不需要分词
4.可能会根据商品商家搜索,商品商家不需要分词
5.可能根据规格进行搜索,规格时一个键值对结构,用Map

根据上面的分析,我们可以在jwang-service-search-api工程中创建com.jwang.search.pojo.SkuInfo,如下

@Document(indexName = "skuinfo",type = "docs")
public class SkuInfo implements Serializable {
//商品id,同时也是商品编号
@Id
private Long id;

//SKU名称
@Field(type = FieldType.Text, analyzer = "ik_smart")
private String name;

//商品价格,单位为:元
@Field(type = FieldType.Double)
private Long price;

//库存数量
private Integer num;

//商品图片
private String image;

//商品状态,1-正常,2-下架,3-删除
private String status;

//创建时间
private Date createTime;

//更新时间
private Date updateTime;

//是否默认
private String isDefault;

//SPUID
private Long spuId;

//类目ID
private Long categoryId;

//类目名称
@Field(type = FieldType.Keyword)
private String categoryName;

//品牌名称
@Field(type = FieldType.Keyword)
private String brandName;

//规格
private String spec;

//规格参数
private Map<String,Object> specMap;

//...略
}

解释:

Field的数据类型
Keyword 表示字符串 关键字类型 ,一定是不分词的
Text 表示字符串 文本类型 可以分词 也可以指定不分词

搜索审核通过Sku

修改jwang-service-goods微服务,添加搜索审核通过的Sku,供search微服务调用。下面都是针对goods微服务的操作。

修改SkuService接口,添加根据状态查询Sku方法,代码如下:

/**
* 根据状态查询SKU列表
*/
List<Sku> findByStatus(String status);

修改SkuServiceImpl,添加根据状态查询Sku实现方法,代码如下:

/***
* 根据状态查询SKU列表
* @return
*/
@Override
public List<Sku> findByStatus(String status) {
Sku sku = new Sku();
sku.setStatus(status);
return skuMapper.select(sku);
}

修改com.jwang.goods.controller.SkuController,添加根据审核状态查询Sku方法,代码如下:

/***
* 根据审核状态查询Sku
* @param status
* @return
*/
@GetMapping("/status/{status}")
public Result<List<Sku>> findByStatus(@PathVariable String status){
List<Sku> list = skuService.findByStatus(status);
return new Result<List<Sku>>(true,StatusCode.OK,"查询成功",list);
}

Sku导入ES实现

(1) Feign配置

修改jwang-service-goods-api工程,在com.jwang.goods.feign.SkuFeign上添加方法

加入依赖:

<dependency>
<groupId>com.jwang</groupId>
<artifactId>jwang-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

如下:

@FeignClient(name="goods",path="/sku")
public interface SkuFeign {

/***
* 根据审核状态查询Sku
* @param status
* @return
*/
@GetMapping("/status/{status}")
Result<List<Sku>> findByStatus(@PathVariable(name="status") String status);
}
@PathVariable(name="status") 一定要声明

(2) Dao创建

修改jwang-service-search工程,创建com.jwang.search.dao.SkuEsMapper,该接口主要用于索引数据操作,主要使用它来实现将数据导入到ES索引库中,代码如下:

@Repository
public interface SkuEsMapper extends ElasticsearchRepository<SkuInfo,Long> {
}

(3) 服务层创建

修改jwang-service-search工程,创建com.jwang.search.service.SkuService,代码如下:

public interface SkuService {

/***
* 导入SKU数据
*/
void importSku();
}

修改jwang-service-search工程,创建com.jwang.search.service.impl.SkuServiceImpl,实现Sku数据导入到ES中,代码如下:

@Service
public class SkuServiceImpl implements SkuService {

@Autowired
private SkuFeign skuFeign;

@Autowired
private SkuEsMapper skuEsMapper;

/**
* 导入sku数据到es
*/
@Override
public void importSku(){
//调用jwang-service-goods微服务
Result<List<Sku>> skuListResult = skuFeign.findByStatus("1");
//将数据转成search.Sku
List<SkuInfo> skuInfos= JSON.parseArray(JSON.toJSONString(skuListResult.getData()),SkuInfo.class);
for(SkuInfo skuInfo:skuInfos){
Map<String, Object> specMap= JSON.parseObject(skuInfo.getSpec()) ;
skuInfo.setSpecMap(specMap);
}
skuEsMapper.saveAll(skuInfos);
}
}

(4)控制层配置

修改jwang-service-search工程,在com.jwang.search.controller.SkuController类中添加如下方法调用上述导入方法,代码如下:

@RestController
@RequestMapping(value = "/search")
@CrossOrigin
public class SkuController {

@Autowired
private SkuService skuService;

/**
* 导入数据
* @return
*/
@GetMapping("/import")
public Result importToES(){
skuService.importSku();
return new Result(true, StatusCode.OK,"导入数据到索引库中成功!");
}
}

(5)修改启动类

启动类中需要开启Feign客户端,并且需要添加ES包扫描,代码如下:

@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
@EnableEurekaClient
@EnableFeignClients(basePackages = "com.jwang.goods.feign")
//@EnableElasticsearchRepositories(basePackages = "com.jwang.search.dao")
public class SearchApplication {

public static void main(String[] args) {
/**
* Springboot整合Elasticsearch 在项目启动前设置一下的属性,防止报错
* 解决netty冲突后初始化client时还会抛出异常
* java.lang.IllegalStateException: availableProcessors is already set to [12], rejecting [12]
***/
System.setProperty("es.set.netty.runtime.available.processors", "false");
SpringApplication.run(SearchApplication.class,args);
}
}

(6)测试

调用http://localhost:18085/search/import进行测试

打开es-head可以看到如下数据:

1560828547924

关键字搜索

1559428874655

我们先使用SpringDataElasticsearch实现一个简单的搜索功能,先实现根据关键字搜索,从上面搜索图片可以看得到,每次搜索的时候,除了关键字外,还有可能有品牌、分类、规格等,后台接收搜索条件使用Map接收比较合适。

服务层实现

修改search服务的com.jwang.search.service.SkuService,添加搜索方法,代码如下:

/***
* 搜索
* @param searchMap
* @return
*/
Map search(Map<String, String> searchMap);

修改search服务的com.jwang.search.service.impl.SkuServiceImpl,添加搜索实现方法,代码如下:

@Autowired
private ElasticsearchTemplate esTemplate;

public Map search(Map<String, String> searchMap) {

//1.获取关键字的值
String keywords = searchMap.get("keywords");

if (StringUtils.isEmpty(keywords)) {
keywords = "华为";//赋值给一个默认的值
}
//2.创建查询对象 的构建对象
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();

//3.设置查询的条件

nativeSearchQueryBuilder.withQuery(QueryBuilders.matchQuery("name", keywords));

//4.构建查询对象
NativeSearchQuery query = nativeSearchQueryBuilder.build();

//5.执行查询
AggregatedPage<SkuInfo> skuPage = esTemplate.queryForPage(query, SkuInfo.class);


//6.返回结果
Map resultMap = new HashMap<>();
resultMap.put("rows", skuPage.getContent());
resultMap.put("total", skuPage.getTotalElements());
resultMap.put("totalPages", skuPage.getTotalPages());

return resultMap;
}

控制层实现

修改com.jwang.search.controller.SkuController,在控制层调用Service层即可,代码如下:

/**
* 搜索
* @param searchMap
* @return
*/
@PostMapping
public Map<String,Object> search(@RequestBody(required = false) Map<String,String> searchMap){
if(searchMap==null){
searchMap = new HashMap<>();
}
return skuService.search(searchMap);
}

测试

使用Postman工具,输入http://localhost:18085/search

选中POST提交

1560829447414