102 全文检索-ElasticSearch-简介

简介

https://www.elastic.co/cn/what-is/elasticsearch

全文搜索属于最常见的需求,开源的 Elasticsearch 是目前全文搜索引擎的首选。它可以快速地储存、搜索和分析海量数据。维基百科、Stack Overflow、Github 都采用它Elastic 的底层是开源库 Lucene。但是,你没法直接用 Lucene,必须自己写代码去调用它的 接口。Elastic 是 Lucene 的封装,提供了 REST API 的操作接口,开箱即用。

REST API:天然的跨平台。

官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html

官方中文:https://www.elastic.co/guide/cn/elasticsearch/guide/current/foreword_id.html

社区中文:

https://es.xiaoleilu.com/index.html

http://doc.codingdict.com/elasticsearch/0/

什么是搜索, 计算机根据用户输入的关键词进行匹配,从已有的数据库中摘录出相关的记录反馈给用户。

常见的全网搜索引擎,像百度、谷歌这样的。但是除此以外,搜索技术在垂直领域也有广泛的使用,比如淘宝、京东搜索商品,万芳、知网搜索期刊,csdn中搜索问题贴。也都是基于海量数据的搜索。

如何处理搜索

1.1用传统关系性数据库

弊端:

1、 对于传统的关系性数据库对于关键词的查询,只能逐字逐行的匹配,性能非常差。

2、匹配方式不合理,比如搜索“小密手机” ,如果用like进行匹配, 根本匹配不到。但是考虑使用者的用户体验的话,除了完全匹配的记录,还应该显示一部分近似匹配的记录,至少应该匹配到“手机”。

1.2专业 全文索引是怎么处理的

全文搜索引擎目前主流的索引技术就是倒排索引的方式。

传统的保存数据的方式都是

记录→单词

而倒排索引的保存数据的方式是

单词→记录

例如

搜索“红海行动”

但是数据库中保存的数据如图:

那么搜索引擎是如何能将两者匹配上的呢?

基于分词技术构建倒排索引:

首先每个记录保存数据时,都不会直接存入数据库。系统先会对数据进行分词,然后以倒排索引结构保存。如下:

然后等到用户搜索的时候,会把搜索的关键词也进行分词,会把“红海行动”分词分成:红海和行动两个词。

这样的话,先用红海进行匹配,得到id=1和id=2的记录编号,再用行动匹配可以迅速定位id为1,3的记录。

那么全文索引通常,还会根据匹配程度进行打分,显然1号记录能匹配的次数更多。所以显示的时候以评分进行排序的话,1号记录会排到最前面。而2、3号记录也可以匹配到。

lucene与elasticsearch

咱们之前讲的处理分词,构建倒排索引,等等,都是这个叫lucene的做的。那么能不能说这个lucene就是搜索引擎呢?还不能。lucene只是一个提供全文搜索功能类库的核心工具包,而真正使用它还需要一个完善的服务框架搭建起来的应用。好比lucene是类似于jdk,而搜索引擎软件就是tomcat 的。目前市面上流行的搜索引擎软件,主流的就两款,elasticsearch和solr,这两款都是基于lucene的搭建的,可以独立部署启动的搜索引擎服务软件。由于内核相同,所以两者除了服务器安装、部署、管理、集群以外,对于数据的操作,修改、添加、保存、查询等等都十分类似。就好像都是支持sql语言的两种数据库软件。只要学会其中一个另一个很容易上手。从实际企业使用情况来看,elasticSearch的市场份额逐步在取代solr,国内百度、京东、新浪都是基于elasticSearch实现的搜索功能。国外就更多了 像维基百科、GitHub、Stack Overflow等等也都是基于ES的

ElasticSearch7-去掉type概念

• 关系型数据库中两个数据表示是独立的,即使他们里面有相同名称的列也不影响使用,但ES 中不是这样的。elasticsearch是基于Lucene开发的搜索引擎,而ES中不同type下名称相同 的filed最终在Lucene中的处理方式是一样的。

• 两个不同type下的两个user_name,在ES同一个索引下其实被认为是同一个filed,你必须在两个不同的type中定义相同的filed映射。否则,不同type中的相同字段名称就会在 处理中出现冲突的情况,导致Lucene处理效率下降。

• 去掉type就是为了提高ES处理数据的效率。

• Elasticsearch 7.x

• URL中的type参数为可选。比如,索引一个文档不再要求提供文档类型。

• Elasticsearch 8.x

• 不再支持URL中的type参数。

• 解决:将索引从多类型迁移到单类型,每种类型文档一个独立索引

一、基本概念

1 Index (索引)

动词,相当于 MySQL 中的 insert;

名词,相当于 MySQL 中的 Database

2 Type (类型)

在 Index(索引)中,可以定义一个或多个类型。

类似于 MySQL 中的 Table;每一种类型的数据放在一起;

3 Document (文档)

保存在某个索引(Index)下,某种类型(Type)的一个数据(Document),文档是 JSON 格

式的,Document 就像是 MySQL 中的某个 Table 里面的内容;

4 、倒排索引机制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dGnW7jW0-1652453219936)(https://gitee.com/jiushuli/images/raw/master/image-20220422215419698.png)]

103 全文检索-ElasticSearch-Docker安装ES

一、 Docker 安装 Es

1 、下载镜像文件

# 存储和检索数据 (在使用的时候要爆出kibana 和 elasticSearch 的版本一致)
docker pull elasticsearch:7.4.2
# 可视化检索数据
docker pull kibana:7.4.2

2 、创建实例

1 ElasticSearch

# 创建配置文件的挂载目录
mkdir -p /mydata/elasticsearch/config
# 创建数据文件的挂载目录
mkdir -p /mydata/elasticsearch/data
# 表示可以接受任何的请求
echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.ymldocker run --name elasticsearch
-p 9200:9200 -p 9300:9300
-e "discovery.type=single-node"
-e ES_JAVA_OPTS="-Xms64m -Xmx512m"
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data
-v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins
-d elasticsearch:7.4.2# 以上的运行参数详解
# --name elasticsearch 容器的名字为elasticsearch
# -p 9200:9200 是elasticsearch通过restful  Api 调用的端口
# -p 9300:9300 是elasticsearch 集群通讯的端口
# -e "discovery.type=single-node" 表示以单机的形式启动
# -e ES_JAVA_OPTS="-Xms64m -Xmx512m" 限制占用的内存,测试环境下,设置 ES 的初始内存和最大内存,否则导 致过大启动不了 ES
# -v 都是对应的挂载配置文件 数据  插件安装 的挂载目录
# -d elasticsearch:7.4.2 以守护式容器的方式运行镜像版本为 7.4.2

特别注意

-e ES_JAVA_OPTS=“-Xms64m -Xmx256m” \ 测试环境下,设置 ES 的初始内存和最大内存,否则导致过大启动不了 ES

测试 http://119.3.105.108:9200

104 全文检索-ElasticSearch-Docker安装kibana

1 **、**Kibana

docker run --name kibana -e ELASTICSEARCH_HOSTS=http://119.3.105.108:9200 -p 5601:5601  -d kibana:7.4.2
# 创建配置文件的挂载目录
# --name kibana 容器的名字
# -e ELASTICSEARCH_HOSTS=http://119.3.105.108:9200 kibana 绑定elasticsearch,一定要写成自己的
# -p 5601:5601 kibana 映射的端口
# -d kibana:7.4.2  创建镜像的版本

测试 http://119.3.105.108:5601

105 全文检索-ElasticSearch-入门_cat

# 查看所有节点
GET /_cat/nodes
# 查看 es 健康状况
GET /_cat/health
# 查看主节点
GET /_cat/master
# 查看所有索引 show databases
GET /_cat/indices

106 全文检索-ElasticSearch–入门-put&post

1 、索引一个文档(保存)

保存一个数据,保存在哪个索引的哪个类型下,指定用哪个唯一标识

PUT customer/external/1 在 customer 索引下的 external 类型下保存 1 号数据为

PUT customer/external/1
{"name": "John Doe"
}结果如下:(红色的提示信息,表示类型已经弃用,es不在使用type,就是直接在index下保存数据了)
#! Deprecation: [types removal] Specifying types in document index requests is deprecated, use the typeless endpoints instead (/{index}/_doc/{id}, /{index}/_doc, or /{index}/_create/{id}).
{"_index" : "customer","_type" : "external","_id" : "1","_version" : 1,"result" : "created","_shards" : {"total" : 2,"successful" : 1,"failed" : 0},"_seq_no" : 0,"_primary_term" : 1
}

PUT 和 POST 都可以新增数据,

POST 新增。如果不指定 id,会自动生成 id。指定 id 就会修改这个数据,并新增版本号

PUT 可以新增可以修改。PUT 必须指定 id;由于 PUT 需要指定 id,我们一般都用来做修改

操作,不指定 id 会报错。

107 全文检索-ElasticSearch-入门-get查询数据&乐观锁字段

查询文档

GET customer/external/1结果如下:
{"_index" : "customer", //在哪个索引"_type" : "external",  //在哪个类型"_id" : "1",           //记录 id"_version" : 1,        //版本号"_seq_no" : 0,         //并发控制字段,每次更新就会+1,用来做乐观锁"_primary_term" : 1,   //同上,主分片重新分配,如重启,就会变化"found" : true,"_source" : {          //真正的内容"name" : "John Doe"}更新携带 ?if_seq_no=0&if_primary_term=1

乐观锁,并发问题参考下一章节的put&post修改数据

108 全文检索-ElasticSearch-入门-put&post修改数据

POST customer/external/1/_update
{"doc": {"name": "John Doew"}
}或者
POST customer/external/1
{"name": "John Doe2"
}或者
PUT customer/external/1
{"name": "John Doe"
}不同:
POST 操作会对比源文档数据,如果相同不会有什么操作,文档 version 不增加
PUT 操作总会将数据重新保存并增加 version 版本; 带_update 对比元数据如果一样就不进行任何操作。 看场景; 对于大并发更新,不带 update; 对于大并发查询偶尔更新,带 update;对比更新,重新计算分配规则。

并发乐观锁演示

//第一步: 先查询出customer索引下type为external中 id为 1 的数据
GET customer/external/1
结果如下:
{"_index" : "customer","_type" : "external","_id" : "1","_version" : 4,"_seq_no" : 3,"_primary_term" : 1,"found" : true,"_source" : {"name" : "John Doe"}
}//第二步:模拟两个并发修改,分被在窗口创建两个更新语(暂不执行语句)句其中_seq_no=3和_primary_term都写上面查出来结果,表示同一个时间两个线程查到了同一条数据,然后进行修改,都是修改的name 字段
POST customer/external/1?if_seq_no=3&if_primary_term=1
{"name": "John Doe3"
}PUT customer/external/1?if_seq_no=3&if_primary_term=1
{"name": "John Doe4"
}//第三步:分别执行第二步的两个语句,只有一个能成功,报错如下
{"error": {"root_cause": [{"type": "version_conflict_engine_exception","reason": "[1]: version conflict, required seqNo [3], primary term [1]. current document has seqNo [4] and primary term [1]","index_uuid": "yHjUvhSjQs2_kMzChT-WKA","shard": "0","index": "customer"}],"type": "version_conflict_engine_exception","reason": "[1]: version conflict, required seqNo [3], primary term [1]. current document has seqNo [4] and primary term [1]","index_uuid": "yHjUvhSjQs2_kMzChT-WKA","shard": "0","index": "customer"},"status": 409
}

更新同时增加属性

POST customer/external/1/_update
{ "doc": { "name": "Jane Doe", "age": 20 }
}PUT 和 POST 不带_update 也可以 如果带了update 则必须用 "doc"

109 全文检索-ElasticSearch-入门-删除数据&bulk批量导入样本测试数据

1、删除数据

//删除customer索引下的类型为external id为1的数据
DELETE customer/external/1
// 直接删除customer索引
DELETE customer//注意es 中没有删除某一个type的操作

2、bulk批量导入数据

//两行为一条数据,第一行的{}块中写元数据(id,分区,等那些信息),第二行才是保存的数据
POST customer/external/_bulk
{"index":{"_id":"1"}}
{"name": "John Doe" }
{"index":{"_id":"2"}}
{"name": "Jane Doe" }语法格式:
{ action: { metadata }}
{ request body }{ action: { metadata }}
{ request body }
//复杂实例:
POST /_bulk
{"delete":{"_index":"website","_type":"blog","_id":"123"}}
{"create":{"_index":"website","_type":"blog","_id":"123"}}
{"title":"My first blog post"}
{"index":{"_index":"website","_type":"blog"}}
{"title":"My second blog post"}
{"update":{"_index":"website","_type":"blog","_id":"123"}}
{"doc":{"title":"My updated blog post"}}bulk API 以此按顺序执行所有的 action(动作)。如果一个单个的动作因任何原因而失败, 它将继续处理它后面剩余的动作。当 bulk API 返回时,它将提供每个动作的状态(与发送 的顺序相同),所以您可以检查是否一个指定的动作是不是失败了。

3、样本数据准备

我准备了一份顾客银行账户信息的虚构的 JSON 文档样本。每个文档都有下列的 schema(模式):

{"account_number": 0,"balance": 16623,"firstname": "Bradshaw","lastname": "Mckenzie","age": 29,"gender": "F","address": "244 Columbus Place","employer": "Euron","email": "bradshawmckenzie@euron.com","city": "Hobucken","state": "CO"
}

导入测试数据链接 (es 官网提供的)如果官网打不开直接百度查就可以了,一定会有帖子给你答案

https://github.com/elastic/elasticsearch/blob/master/docs/src/test/resources/accounts.json?raw=true

110 全文检索-ElasticSearch-进阶-两种查询方式

1 SearchAPI

ES 支持两种基本方式检索 :

  • 一个是通过使用 REST request URI 发送搜索参数(uri+检索参数)

  • 另一个是通过使用 REST request body 来发送它们(uri+请求体)

1 )、检索信息

  • 一切检索从_search 开始
GET bank/_search       //检索 bank 下所有信息,包括 type 和 docsGET bank/_search?q=*&sort=account_number:asc    //请求参数方式检索响应结果解释:
took - Elasticsearch 执行搜索的时间(毫秒)
time_out - 告诉我们搜索是否超时
_shards - 告诉我们多少个分片被搜索了,以及统计了成功/失败的搜索分片
hits - 搜索结果
hits.total - 搜索结果
hits.hits - 实际的搜索结果数组(默认为前 10 的文档)
sort - 结果的排序 key(键)(没有则按 score 排序)
score 和 max_score –相关性得分和最高得分(全文检索用)
//uri+请求体进行检索
GET bank/_search
{"query": {"match_all": {}},"sort": [{"account_number": {"order": "desc"}}]
}HTTP 客户端工具(POSTMAN),get 请求不能携带请求体,我们变为 post 也是一样的 我们 POST 一个 JSON 风格的查询请求体到 _search API。 需要了解,一旦搜索的结果被返回,Elasticsearch 就完成了这次请求,并且不会维护任何 服务端的资源或者结果的 cursor(游标)

111 全文检索-ElasticSearch-进阶-QueryDSL基本使用&match_all

Query DSL

1 )、基本语法格式

Elasticsearch 提供了一个可以执行查询的 Json 风格的 DSLdomain-specific language 领域特定语言)。这个被称为 Query DSL。该查询语言非常全面,并且刚开始的时候感觉有点复杂, 真正学好它的方法是从一些基础的示例开始的。

一个查询语句 的典型结构 :

{ QUERY_NAME: { ARGUMENT: VALUE, ARGUMENT: VALUE,... }
}

如果是针对某个字段,那么它的结构如下:

{ QUERY_NAME: { FIELD_NAME: { ARGUMENT: VALUE, ARGUMENT: VALUE,... } }
}
GET bank/_search
{"query": {"match_all": {}},"from": 0,"size": 5,"sort": [{"account_number": {"order": "desc"}}]
}query 定义如何查询,
match_all 查询类型【代表查询所有的所有】,es 中可以在 query 中组合非常多的查 询类型完成复杂查询除了 query 参数之外,我们也可以传递其它的参数以改变查询结果。如 sort,size
from+size 限定,完成分页功能
sort 排序,多字段排序,会在前序字段相等时后续字段内部排序,否则以前序为准

2 )、返回部分字段

GET bank/_search
{"query": {"match_all": {}},"from": 0,"size": 5,"_source": ["age", "balance"]
}
//_source 里面写了什么字段就会只返回什么字段

112 全文检索-ElasticSearch-进阶-match全文匹配

match 【匹配查询】

# 基本类型(非字符串),精确匹配
GET bank/_search
{"query": {"match": {"account_number": "20"}}
}match 返回 account_number=20 的# 字符串,全文检索
GET bank/_search
{"query": {"match": {"address": "mill"}}
}最终查询出 address 中包含 mill 单词的所有记录 match 当搜索字符串类型的时候,会进行全文检索,并且每条记录有相关性得分。# 字符串,多个单词(分词+全文检索)
GET bank/_search
{"query": {"match": {"address": "mill road"}}
}最终查询出 address 中包含 mill 或者 road 或者 mill road 的所有记录,并给出相关性得分

113 全文检索-ElasticSearch-进阶-match_phrase短语匹配

# 将需要匹配的值当成一个整体单词(不分词)进行检索
GET bank/_search
{"query": {"match_phrase": {"address": "mill road"}}
}查出 address 中包含 mill road 的所有记录,并给出相关性得分

114 全文检索-ElasticSearch-进阶-multi_match多字段匹配

# multi_match【多字段匹配】
GET bank/_search
{"query": {"multi_match": {"query": "mill","fields": ["state","address"]}}
}state 或者 address 包含 mill

115 全文检索-ElasticSearch-进阶-bool复合查询

bool 用来做复合查询:

复合语句可以合并 任何 其它查询语句,包括复合语句,了解这一点是很重要的。这就意味着,复合语句之间可以互相嵌套,可以表达非常复杂的逻辑。

# must:必须达到 must 列举的所有条件
GET bank/_search
{"query": {"bool": {"must": [{"match": {"address": "mill"}},{"match": {"gender": "M"}}]}}
}# should:应该达到 should 列举的条件,如果达到会增加相关文档的评分,并不会改变 查询的结果。如果 query 中只有 should 且只有一种匹配规则,那么 should 的条件就会 被作为默认匹配条件而去改变查询结果
GET bank/_search
{"query": {"bool": {"must": [{"match": {"address": "mill"}},{"match": {"gender": "M"}}],"should": [{"match": {"address": "lane"}}]}}
}# must_not 必须不是指定的情况
GET bank/_search
{"query": {"bool": {"must": [{"match": {"address": "mill"}},{"match": {"gender": "M"}}],"should": [{"match": {"address": "lane"}}],"must_not": [{"match": {"email": "baluba.com"}}]}}
}address 包含 mill,并且 gender 是 M,如果 address 里面有 lane 最好不过,但是 email 必 须不包含 baluba.com

116 全文检索-ElasticSearch-进阶-filter过滤

并不是所有的查询都需要产生分数,特别是那些仅用于 “filtering”(过滤)的文档。为了不计算分数 Elasticsearch 会自动检查场景并且优化查询的执行。

# 并不是所有的查询都需要产生分数,特别是那些仅用于 “filtering”(过滤)的文档。为了不 计算分数 Elasticsearch 会自动检查场景并且优化查询的执行。
GET bank/_search
{"query": {"bool": {"must": [{"match": {"address": "mill"}}],"filter": {"range": {"balance": {"gte": 10000,"lte": 20000}}}}}
}

117 全文检索-ElasticSearch-进阶-term查询

和 match 一样。匹配某个属性的值。全文检索字段用 match其他非 text 字段匹配用 term

GET bank/_search
{"query": {"bool": {"must": [{"term": {"age": {"value": "28"}}},{"match": {"address": "990 Mill Road"}}]}}
}

118 全文检索-ElasticSearch-进阶-aggregations聚合分析

聚合提供了从数据中分组和提取数据的能力。最简单的聚合方法大致等于 SQL GROUP BY 和 SQL 聚合函数。在 Elasticsearch 中,您有执行搜索返回 hits(命中结果),并且同时返 回聚合结果,把一个响应中的所有 hits(命中结果)分隔开的能力。这是非常强大且有效的, 您可以执行查询和多个聚合,并且在一次使用中得到各自的(任何一个的)返回结果,使用 一次简洁和简化的 API 来避免网络往返。

// 查询地址包含 mill的,在根据年龄分布(分组,term中的size就是取100种可能的值,不写就是显示所有的可能的值),然后再聚合一个年龄的平均年龄
GET bank/_search
{"query": {"match": {"address": "mill"}},"aggs": {"group_by_state": {"terms": {"field": "age","size":100}},"avg_age": {"avg": {"field": "age"}}},"size": 0
}size:0 不显示搜索数据
aggs:执行聚合。聚合语法如下
"aggs": { "aggs_name 这次聚合的名字,方便展示在结果集中": { "AGG_TYPE 聚合的类型(avg,term,terms)": {} }
},
//复杂: 按照年龄聚合,并且请求这些年龄段的这些人的平均薪资
GET bank/account/_search
{"query": {"match_all": {}},"aggs": {"age_avg": {"terms": {"field": "age","size": 1000},"aggs": {"banlances_avg": {"avg": {"field": "balance"}}}}},"size": 1000
}
//复杂:查出所有年龄分布,并且这些年龄段中 M 的平均薪资和 F 的平均薪资以及这个年龄 段的总体平均薪资
GET bank/account/_search
{"query": {"match_all": {}},"aggs": {"age_agg": {"terms": {"field": "age","size": 100},"aggs": {"gender_agg": {"terms": {"field": "gender.keyword","size": 100},"aggs": {"balance_avg": {"avg": {"field": "balance"}}}},"balance_avg": {"avg": {"field": "balance"}}}}},"size": 1000
}

具体查看es的聚合函数中有多少种聚合可以查看官网。

119 全文检索-ElasticSearch-映射-映射-mapping创建

1 )、字段类型

# 核心类型
1、字符串 (string)text ,keyword2、数字类型(Numeric)long,integer,short,byte,double,float,half,scaled_float3、日期类型(Date)date4、布尔类型(Boolean)boolean5、二进制类型(binary)binary# 复合类型
1、数组类型(Array)Array支持不针对特定的类型2、对象性(Object)object用于单json对象3、嵌套类型(Nested)nested 用于json对象数组# 地理类型(Geo)
1、地理坐标(Geo-points)geo_point 用于描述经纬度坐标2、地理图形(Geo-Shape)geo_shape用于描述复复杂形状,如多变形# 特定类型
1、IP类型ip用于描述Ipv4 和 Ipv6地址
2、补全类型(Completion)completion提供自动完成提示
3、令牌计数类型(Token Count)token_count用于统计字符串中的词条数量
4、附件类型(Attachment)参考mapper-attachements插件,支持将附件如 Microsoft Office格式,Open Document格式,ePub,Html等等索引为attachement数据类型
5、抽取类型(Percolator)接受特定领域查询语言(query-dsl)的查询# 多字段通常用于不同目的用不同的方法索引同一个字段,列如 string 字段可以映射为一个text字段用于索引,同样可以映射为一个keyword字段用于排序和聚合,另外,你可以使用standard analyzer,english analyzer,french analyzer来索引一个text字段这就是muti-fields的目的,大多数的数据类型通过fileds参数支持muti-fields.

2 )、映射

Mapping(映射)

Mapping 是用来定义一个文档(document),以及它所包含的属性(field)是如何存储和

索引的。比如,使用 mapping 来定义:

  • 哪些字符串属性应该被看做全文本属性(full text fields)。

  • 哪些属性包含数字,日期或者地理位置。

  • 文档中的所有属性是否都能被索引(_all 配置)。

  • 日期的格式。

  • 自定义映射规则来执行动态添加属性。

# 查看 mapping 信息:
GET bank/_mapping
# 修改 mapping 信息
https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html

自动猜测的映射类型

3 )、新版本改变

Es7 及以上移除了 type 的概念。

  • 关系型数据库中两个数据表示是独立的,即使他们里面有相同名称的列也不影响使用, 但 ES 中不是这样的。elasticsearch 是基于 Lucene 开发的搜索引擎,而 ES 中不同 type 下名称相同的 filed 最终在 Lucene 中的处理方式是一样的。

  • 两个不同 type 下的两个 user_name,在 ES 同一个索引下其实被认为是同一个 filed, 你必须在两个不同的 type 中定义相同的 filed 映射。否则,不同 type 中的相同字段 名称就会在处理中出现冲突的情况,导致 Lucene 处理效率下降。

  • 去掉 type 就是为了提高 ES 处理数据的效率。

Elasticsearch 7.x

  • URL 中的 type 参数为可选。比如,索引一个文档不再要求提供文档类型。 Elasticsearch 8.x

  • 不再支持 URL 中的 type 参数。

解决:

1)、将索引从多类型迁移到单类型,每种类型文档一个独立索引

2)、将已存在的索引下的类型数据,全部迁移到指定位置即可。详见数据迁移

120 全文检索-ElasticSearch-映射-添加新的字段映射

1 、创建映射

//创建索引并指定映射
PUT /my-index
{"mappings": {"properties": {"age": {"type": "integer"},"email": {"type": "keyword"},"name": {"type": "text"}}}
}

2 、添加新的字段映射

//添加新的字段映射
PUT /my-index/_mapping
{"properties": {"employee-id": {"type": "keyword","index": false}}
}

121 全文检索-ElasticSearch-映射-修改映射&数据迁移

1 、更新映射

对于已经存在的映射字段,我们不能更新。更新必须创建新的索引进行数据迁移

2、数据迁移

先创建出 new_twitter 的正确映射。然后使用如下方式进行数据迁移

//索引下没有类型的迁移,source源索引,dest目标索引
POST _reindex [固定写法]
{ "source": { "index": "twitter" },"dest": { "index": "new_twitter"}
}//索引下没有有类型的迁移,source源索引哪个type下的数据迁移到目标的索引,dest目标索引
将旧索引的 type 下的数据进行迁移
POST _reindex { "source": {"index": "twitter", "type": "tweet" },"dest": { "index": "tweets" }
}

122 全文检索-ElasticSearch-分词-安装ik分词

一个 tokenizer(分词器)接收一个字符流,将之分割为独立的 tokens(词元,通常是独立 的单词),然后输出 tokens 流。

例如,whitespace tokenizer 遇到空白字符时分割文本。它会将文本 “Quick brown fox!” 分割 \为 [Quick, brown, fox!]。 该 tokenizer(分词器)还负责记录各个 term(词条)的顺序或 position 位置(用于 phrase 短 语和 word proximity 词近邻查询),以及 term(词条)所代表的原始 word(单词)的 start (起始)和 end(结束)的 character offsets(字符偏移量)(用于高亮显示搜索的内容)。

Elasticsearch 提供了很多内置的分词器,可以用来构建 custom analyzers(自定义分词器)。

1 )、安装 ik 分词器

**注意:**不能用默认 elasticsearch-plugin

install xxx.zip 进行自动安装

https://github.com/medcl/elasticsearch-analysis-ik/releases?after=v6.4.2 对应 es 版本安装

进入 es 容器内部 plugins 目录 (如果之前讲es的插件目录挂载到外面的话 都不用进入容器的内部)
docker exec -it 容器 id /bin/bash
wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip
unzip 下载的文件
rm –rf *.zip
mv elasticsearch/ ik
可以确认是否安装好了分词器
cd ../bin
elasticsearch plugin list:即可列出系统的分词器
# 执行步骤如下
1、cd 到自己挂载的plugins的目录下
[root@ecs-284198 ~]# cd /mydata/elasticsearch/plugins2、在线下载分词器的(也可以现在后上传)
[root@ecs-284198 plugins]# wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip3、查看下载完成的插件
[root@ecs-284198 plugins]# pwd
/mydata/elasticsearch/plugins
[root@ecs-284198 plugins]# ll
total 4404
-rw-r--r-- 1 root root 4504487 Dec  8 01:01 elasticsearch-analysis-ik-7.4.2.zip
[root@ecs-284198 plugins]#4、解压分词器
[root@ecs-284198 plugins]# unzip elasticsearch-analysis-ik-7.4.2.zip5、在plugins目录下新建一个ik文件夹,然后将所有解压出来的移动到ik文件夹下
先删除zip
[root@ecs-284198 plugins]# rm -rf *.zip
[root@ecs-284198 plugins]# mv ./commons-codec-1.9.jar commons-logging-1.2.jar config elasticsearch-analysis-ik-7.4.2.jar httpclient-4.5.2.jar httpcore-4.4.4.jar plugin-descriptor.properties plugin-security.policy  ik[root@ecs-284198 plugins]# cd ik
[root@ecs-284198 ik]# ll
total 1432
-rw-r--r-- 1 root root 263965 May  6  2018 commons-codec-1.9.jar
-rw-r--r-- 1 root root  61829 May  6  2018 commons-logging-1.2.jar
drwxr-xr-x 2 root root   4096 Oct  7  2019 config
-rw-r--r-- 1 root root  54643 Nov  4  2019 elasticsearch-analysis-ik-7.4.2.jar
-rw-r--r-- 1 root root 736658 May  6  2018 httpclient-4.5.2.jar
-rw-r--r-- 1 root root 326724 May  6  2018 httpcore-4.4.4.jar
-rw-r--r-- 1 root root   1805 Nov  4  2019 plugin-descriptor.properties
-rw-r--r-- 1 root root    125 Nov  4  2019 plugin-security.policy
[root@ecs-284198 ik]#6、进入容器内部在进入bin目录
[root@ecs-284198 ik]# docker exec -it 06de913c848e /bin/bash
[root@06de913c848e elasticsearch]# cd bin7、查看已经安装好的插件
[root@06de913c848e bin]# elasticsearch-plugin list
ik
[root@06de913c848e bin]#---------------完成---------------------
重启es  docker 容器

2 )、测试分词器

当前步骤是基于上一步安装了ik 分词器的

使用默认

POST _analyze
{
"text": "我是中国人"
}

请观察结果

使用分词器

POST _analyze
{ "analyzer": "ik_smart", "text": "我是中国人"
}

请观察结果

另外一个分词器 ik_max_word

POST _analyze
{ "analyzer": "ik_max_word", "text": "我是中国人"
}

请观察结果

能够看出不同的分词器,分词有明显的区别,所以以后定义一个索引不能再使用默认的 mapping 了,要手工建立 mapping, 因为要选择分词器。

123 补充-修改Linux网络设置&开启root密码访问

略。。。

124 全文检索-ElasticSearch-分词-自定义分词扩展库

修改/usr/share/elasticsearch/plugins/ik/config/中的 IKAnalyzer.cfg.xml
/usr/share/elasticsearch/plugins/ik/config
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment> <!--用户可以在这里配置自己的扩展字典 --> <entry key="ext_dict"></entry> <!--用户可以在这里配置自己的扩展停止词字典--> <entry key="ext_stopwords"></entry> <!--用户可以在这里配置远程扩展字典 --> <entry key="remote_ext_dict">http://192.168.128.130/fenci/myword.txt</entry><!--用户可以在这里配置远程扩展停止词字典--><!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
http://192.168.128.130/fenci/myword.txt 就是我们部署到服务器上一个nginx下的一个文件,里面写入了我们需要扩展词库,如何安装装nginx 参照下面的附录部分原来的 xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties> <comment>IK Analyzer 扩展配置</comment><!--用户可以在这里配置自己的扩展字典 --> <entry key="ext_dict"></entry> <!--用户可以在这里配置自己的扩展停止词字典--> <entry key="ext_stopwords"></entry> <!--用户可以在这里配置远程扩展字典 --><!-- <entry key="remote_ext_dict">words_location</entry> --> <!--用户可以在这里配置远程扩展停止词字典--> <!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

按照标红的路径利用 nginx 发布静态资源,按照请求路径,创建对应的文件夹以及文件,放在nginx 的 html 下

然后重启 es 服务器,重启 nginx。 在 kibana 中测试分词效果

更新完成后,es 只会对新增的数据用新词分词。历史数据是不会重新分词的。如果想要历史数据重新分词。需要执行:

POST my_index/_update_by_query?conflicts=proceed

附录 - 安装 nginx

  • 随便启动一个 nginx 实例,只是为了复制出配置

  • docker run -p 80:80 --name nginx -d nginx:1.10

  • 将容器内的配置文件拷贝到当前目录:docker container cp nginx:/etc/nginx .

  • 别忘了后面的点

  • 修改文件名称:mv nginx conf 把这个 conf 移动到/mydata/nginx 下

  • 终止原容器:docker stop nginx

  • 执行命令删除原容器:docker rm $ContainerId

  • 创建新的 nginx;执行以下命令

docker run -p 80:80 --name nginx -v /mydata/nginx/html:/usr/share/nginx/html -v /mydata/nginx/logs:/var/log/nginx -v /mydata/nginx/conf:/etc/nginx  -d nginx:1.10
  • 给 nginx 的 html 下面放的所有资源可以直接访问;

125 全文检索-ElasticSearch-整合-springboot整合high-level-client

1)、9300:TCP

  • spring-data-elasticsearch:transport-api.jar;

  • springboot 版本不同, transport-api.jar 不同,不能适配 es 版本 7.x 已经不建议使用,8 以后就要废弃

2)、9200:HTTP

  • JestClient:非官方,更新慢

  • RestTemplate:模拟发 HTTP 请求,ES 很多操作需要自己封装,麻烦

  • HttpClient:同上

  • Elasticsearch-Rest-Client:官方 RestClient,封装了 ES 操作,API 层次分明,上手简单

最终选择 Elasticsearch-Rest-Client(elasticsearch-rest-high-level-client)

https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high.html

1、在项目中新建gulimall-search 模块

2 SpringBoot 整合

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.atguigu.gulimall</groupId><artifactId>gulimall-common</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId><version>7.4.2</version></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId><version>3.1.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>

引入es的maven依赖后,查看jar包下所有的版本,发现还有两个版本是不是7.4.2的,有两个是7.15.2的这是因为我们的springboot的starter默认有相关的依赖,我们只需要加上指定版本<elasticsearch.version>7.4.2</elasticsearch.version> 即可,然后刷新一下

3 、配置

3.1 配置类GulimallElasticSearchConfig

package com.atguigu.gulimall.search.config;import org.apache.http.HttpHost;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** @创建人: 放生* @创建时间: 2022/4/23* @描述:*/
@Configuration
public class GulimallElasticSearchConfig {public static final RequestOptions COMMON_OPTIONS;static {RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
//        builder.addHeader("Authorization", "Bearer " + TOKEN);
//        builder.setHttpAsyncResponseConsumerFactory(
//                new HttpAsyncResponseConsumerFactory
//                        .HeapBufferedResponseConsumerFactory(30 * 1024 * 1024 * 1024));COMMON_OPTIONS = builder.build();}@Beanpublic RestHighLevelClient esRestClient(@Value("${spring.elasticsearch.jest.uris}")String esUrl){//TODO 修改为线上的地址RestClientBuilder builder = null;//final String hostname, final int port, final String scheme//        builder = RestClient.builder(new HttpHost("119.3.105.108", 9200, "http"));builder = RestClient.builder(HttpHost.create(esUrl));RestHighLevelClient client = new RestHighLevelClient(builder);
//        RestHighLevelClient client = new RestHighLevelClient(
//                RestClient.builder(
//                        new HttpHost("119.3.105.108", 9200, "http")));return client;}}

3.2 配置文件

bootstrap.properties

spring.application.name=gulimall-search
spring.cloud.nacos.config.server-addr=119.3.105.108:8848
spring.cloud.nacos.config.namespace=faf54575-dedd-455f-80cc-0d90933b23d9
spring.elasticsearch.jest.uris=http://119.3.105.108:9200

application.yml

server:port: 12000spring:application:name: gulimall-searchcloud:nacos:discovery:server-addr: 119.3.105.108:8848

主启动

package com.atguigu.gulimall.search;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;@EnableDiscoveryClient
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class GulimallSearchApplication {public static void main(String[] args) {SpringApplication.run(GulimallSearchApplication.class, args);}}

4、使用

参照官方文档:更多的java api 的操作文档可以参照官网

package com.atguigu.gulimall.search;import com.alibaba.fastjson.JSON;
import com.atguigu.gulimall.search.config.GulimallElasticSearchConfig;
import lombok.Data;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.io.IOException;@SpringBootTest
class GulimallSearchApplicationTests {@Autowiredprivate RestHighLevelClient esRestClient;@Testvoid contextLoads() {System.out.println(esRestClient);}/*** 测试存储数据到es* 更新也可以*/@Testpublic void indexData() throws IOException {IndexRequest indexRequest = new IndexRequest("users");indexRequest.id("1");//数据的id
//        indexRequest.source("userName","zhangsan","age",18,"gender","男");User user = new User();user.setUserName("zhangsan");user.setAge(18);user.setGender("男");String jsonString = JSON.toJSONString(user);indexRequest.source(jsonString, XContentType.JSON);//要保存的内容//执行操作IndexResponse index = esRestClient.index(indexRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);//提取有用的响应数据System.out.println(index);}@Dataclass User{private String userName;private String gender;private Integer age;}}

测试 GET users/_search

@Test
void test1() throws IOException { Product product = new Product(); product.setSpuName("华为"); product.setId(10L); IndexRequest request = new IndexRequest("product").id("20") .source("spuName","华为","id",20L); try { IndexResponse response = client.index(request, RequestOptions.DEFAULT); System.out.println(request.toString());IndexResponse response2 = client.index(request, RequestOptions.DEFAULT); } catch (ElasticsearchException e) {if (e.status() == RestStatus.CONFLICT) { } }
}

126 全文检索-ElasticSearch-整合-测试保存

上一章节已经演示

127 全文检索-ElasticSearch-整合-测试复杂检索

package com.atguigu.gulimall.search;import com.alibaba.fastjson.JSON;
import com.atguigu.gulimall.search.config.GulimallElasticSearchConfig;
import lombok.Data;
import lombok.ToString;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.Avg;
import org.elasticsearch.search.aggregations.metrics.AvgAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.io.IOException;@SpringBootTest
class GulimallSearchApplicationTests {@Autowiredprivate RestHighLevelClient esRestClient;@ToString@Datastatic class  Accout {private int account_number;private int balance;private String firstname;private String lastname;private int age;private String gender;private String address;private String employer;private String email;private String city;private String state;}@Testpublic void searchData() throws IOException {//1、创建检索请求SearchRequest searchRequest = new SearchRequest();//指定索引searchRequest.indices("bank");//指定DSL,检索条件//SearchSourceBuilder sourceBuilde 封装的条件SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();//1.1)、构造检索条件
//        sourceBuilder.query();
//        sourceBuilder.from();
//        sourceBuilder.size();
//        sourceBuilder.aggregation()sourceBuilder.query(QueryBuilders.matchQuery("address","mill"));//1.2)、按照年龄的值分布进行聚合TermsAggregationBuilder ageAgg = AggregationBuilders.terms("ageAgg").field("age").size(10);sourceBuilder.aggregation(ageAgg);//1.3)、计算平均薪资AvgAggregationBuilder balanceAvg = AggregationBuilders.avg("balanceAvg").field("balance");sourceBuilder.aggregation(balanceAvg);System.out.println("检索条件"+sourceBuilder.toString());searchRequest.source(sourceBuilder);//2、执行检索;SearchResponse searchResponse = esRestClient.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);//3、分析结果 searchResponseSystem.out.println(searchResponse.toString());
//        Map map = JSON.parseObject(searchResponse.toString(), Map.class);//3.1)、获取所有查到的数据SearchHits hits = searchResponse.getHits();SearchHit[] searchHits = hits.getHits();for (SearchHit hit : searchHits) {/*** "_index": "bank",*           "_type": "account",*            "_id": "345",*          "_score": 5.4032025,*             "_source":*/
//            hit.getIndex();hit.getType();hit.getId();String string = hit.getSourceAsString();Accout accout = JSON.parseObject(string, Accout.class);System.out.println("accout:"+accout);}//3.2)、获取这次检索到的分析信息;Aggregations aggregations = searchResponse.getAggregations();
//        for (Aggregation aggregation : aggregations.asList()) {//            System.out.println("当前聚合:"+aggregation.getName());
            aggregation.get
//
//        }Terms ageAgg1 = aggregations.get("ageAgg");for (Terms.Bucket bucket : ageAgg1.getBuckets()) {String keyAsString = bucket.getKeyAsString();System.out.println("年龄:"+keyAsString+"==>"+bucket.getDocCount());}Avg balanceAvg1 = aggregations.get("balanceAvg");System.out.println("平均薪资:"+balanceAvg1.getValue());//        Aggregation balanceAvg2 = aggregations.get("balanceAvg");}}

128 商城业务-商品上架-sku在es中存储模型分析

    # 整个商城的业务的商品在es中的存储模型大概有两种,先脑海中想一下京东商城首页,第一种是用宽表冗余存储,一种是多表# 方案一: 假设我们的商城中以华为手机Mate40为列,可能会有下面的情况:{skuId:1spuId:11skuTitle:华为 Mata 40price:7999saleCount:99attrs:[{尺寸:5寸},{CPU:高通945},{分辨率:全高清}]},{skuId:2spuId:11skuTitle:华为 Mata 40price:7999saleCount:98attrs:[{尺寸:4.5寸},{CPU:高通945},{分辨率:全高清}]}{skuId:3spuId:11skuTitle:华为 Mata 40price:7999saleCount:99attrs:[{尺寸:5.5寸},{CPU:高通945},{分辨率:全高清}]},只列举了三个sku 发现他们的spu 等很多信息是相同的,但是我们每条数据都要冗余,假如每条数据有20kb冗余,如果100万个商品那么就会多出20G 的数据,这就是牺牲空间换时间(1)、方便检索{skuId:1spuId:11skuTitle:华为xxprice:998saleCount:99attrs:[{尺寸:5寸},{CPU:高通945},{分辨率:全高清}]}冗余:100万20=10000002KB=2000MB=2G 20# 方案二: 就是不冗余,分表,先查出部分数据后然后联动查出其他需要的数据,这样是节省了空间,但是效率很慢(2)、sku索引{skuId:1spuId:11xxxxx}attr索引{spuId:11,attrs:[{尺寸:5寸},{CPU:高通945},{分辨率:全高清}]}搜索 小米; 粮食,手机,电器。10000个,4000个spu分步,4000个spu对应的所有可能属性;esClient: spuId:[4000个spuid] 40008=32000byte=32kb32kb10000=32000mb;=32GB

1 根据业务搭建数据结构

这时我们要思考三个问题:

1、 哪些字段需要分词

2、 我们用哪些字段进行过滤

3、 哪些字段我们需要通过搜索显示出来。

需要分词的字段 sku名称 sku描述 分词、定义分词器
有可能用于过滤的字段 平台属性、三级分类、价格 要索引
其他需要显示的字段 skuId 图片路径 不索引

2、最终的结构

根据以上制定出如下结构:

PUT gmall
{"mappings": {"SkuInfo": {"properties": {"id": {"type": "keyword","index": false},"price": {"type": "double"},"skuName": {"type": "text","analyzer": "ik_max_word"},"skuDesc": {"type": "text","analyzer": "ik_smart"},"catalog3Id": {"type": "keyword"},"skuDefaultImg": {"type": "keyword","index": false},"skuAttrValueList": {"properties": {"valueId": {"type": "keyword"}}}}}}
}

129 商城业务-商品上架-nested数据类型场景

1、nested 扁平化处理

如图:我们在没有处理扁平化之前,在es中保存了这样一条数据,这条数据有一个属性是一个对象的集合 user, 这个属性的集合有两条数据,但是属性user的type没有指定为nested的时候,es会扁平化处理,会把所有的user.first放到里面的一个数组中,所有的user.last也放到一个数组中,这样我们查询一个 user.first=Alice user.last=Smith 就会查询出数据,要避免这个问题,所有的数据的属性为对象集合的字段,类型都要申明为nested的

# 演示:
# 创建my_index索引并存入数据
PUT my_index/_doc/1
{"group":"fans","user":[{"first":"John","last":"Smith"},{"first":"Alice","last":"White"}]}# 查询
GET my_index/_search
{"query": {"bool": {"must": [{"match": {"user.first": "John"}},{"match": {"user.last": "White"}}]}}
}# 结果  果真能查询
{"took" : 6,"timed_out" : false,"_shards" : {"total" : 1,"successful" : 1,"skipped" : 0,"failed" : 0},"hits" : {"total" : {"value" : 1,"relation" : "eq"},"max_score" : 0.5753642,"hits" : [{"_index" : "my_index","_type" : "_doc","_id" : "1","_score" : 0.5753642,"_source" : {"group" : "fans","user" : [{"first" : "John","last" : "Smith"},{"first" : "Alice","last" : "White"}]}}]}
}# -----------------------------------------修改 --------------------------------------
# 查看修改之前的mapping
GET my_index/_mapping
# 删除之前的索引
DELETE my_index
# 新建索引  并且指定 user的类型为nested
PUT my_index
{"mappings": {"properties": {"user":{"type": "nested"}}}
# 再存入数据
PUT my_index/_doc/1
{"group":"fans","user":[{"first":"John","last":"Smith"},{"first":"Alice","last":"White"}]}# 再次查询
GET my_index/_search
{"query": {"bool": {"must": [{"match": {"user.first": "John"}},{"match": {"user.last": "White"}}]}}
}
# 结果接没有了

130 商城业务-商品上架-构造基本数据

1、根据我们128节课程的最终结构 ,构建对应的model

在common 的to包下新建es包,创建SkuEsModel类

package com.atguigu.common.to.es;/*** @创建人: 放生* @创建时间: 2022/4/23* @描述:*/import lombok.Data;import java.math.BigDecimal;
import java.util.List;/*** "properties": {*       "attrs": {*         "type": "nested",*         "properties": {*           "attrId": {*             "type": "long"*           },*           "attrName": {*             "type": "keyword",*             "index": false,*             "doc_values": false*           },*           "attrValue": {*             "type": "keyword"*           }*         }*       }*     }*   }*/
@Data
public class SkuEsModel {private Long skuId;private Long spuId;private String skuTitle;private BigDecimal skuPrice;private String skuImg;private Long saleCount;private Boolean hasStock;private Long hotScore;private Long brandId;private Long catalogId;private String brandName;private String brandImg;private String catalogName;private List<Attrs> attrs;@Datapublic static class Attrs{private Long attrId;private String attrName;private String attrValue;}}

2、SpuInfoController 新增上架方法up

  ///product/spuinfo/{spuId}/up@PostMapping("/{spuId}/up")public R spuUp(@PathVariable("spuId") Long spuId){spuInfoService.up(spuId);return R.ok();}

3、SpuInfoService

public interface SpuInfoService extends IService<SpuInfoEntity> {......+  void up(Long spuId);
}

4、SpuInfoServiceImpl

 @Overridepublic void up(Long spuId) {//1、查出当前spuid对应的所有sku信息,品牌的名字。List<SkuInfoEntity> skus = skuInfoService.getSkusBySpuId(spuId);List<Long> skuIdList = skus.stream().map(SkuInfoEntity::getSkuId).collect(Collectors.toList());//TODO 4、查询当前sku的所有可以被用来检索的规格属性,List<ProductAttrValueEntity> baseAttrs = attrValueService.baseAttrlistforspu(spuId);List<Long> attrIds = baseAttrs.stream().map(attr -> {return attr.getAttrId();}).collect(Collectors.toList());List<Long> searchAttrIds = attrService.selectSearchAttrIds(attrIds);Set<Long> idSet = new HashSet<>(searchAttrIds);List<SkuEsModel.Attrs> attrsList = baseAttrs.stream().filter(item -> {return idSet.contains(item.getAttrId());}).map(item -> {SkuEsModel.Attrs attrs1 = new SkuEsModel.Attrs();BeanUtils.copyProperties(item, attrs1);return attrs1;}).collect(Collectors.toList());//TODO 1、发送远程调用,库存系统查询是否有库存Map<Long, Boolean> stockMap = null;try{R r = wareFeignService.getSkusHasStock(skuIdList);//TypeReference<List<SkuHasStockVo>> typeReference = new TypeReference<List<SkuHasStockVo>>() {};stockMap = r.getData(typeReference).stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, item -> item.getHasStock()));}catch (Exception e){log.error("库存服务查询异常:原因{}",e);}//2、封装每个sku的信息Map<Long, Boolean> finalStockMap = stockMap;List<SkuEsModel> upProducts = skus.stream().map(sku -> {//组装需要的数据SkuEsModel esModel = new SkuEsModel();BeanUtils.copyProperties(sku,esModel);//skuPrice,skuImg,esModel.setSkuPrice(sku.getPrice());esModel.setSkuImg(sku.getSkuDefaultImg());//hasStock,hotScore//设置库存信息if(finalStockMap == null){esModel.setHasStock(true);}else {esModel.setHasStock(finalStockMap.get(sku.getSkuId()));}//TODO 2、热度评分。0esModel.setHotScore(0L);//TODO 3、查询品牌和分类的名字信息BrandEntity brand = brandService.getById(esModel.getBrandId());esModel.setBrandName(brand.getName());esModel.setBrandImg(brand.getLogo());CategoryEntity category = categoryService.getById(esModel.getCatalogId());esModel.setCatalogName(category.getName());//设置检索属性esModel.setAttrs(attrsList);return esModel;}).collect(Collectors.toList());//TODO 5、将数据发送给es进行保存;gulimall-search;R r = searchFeignService.productStatusUp(upProducts);if(r.getCode() == 0){//远程调用成功//TODO 6、修改当前spu的状态baseMapper.updateSpuStatus(spuId, ProductConstant.StatusEnum.SPU_UP.getCode());}else {//远程调用失败//TODO 7、重复调用?接口幂等性;重试机制?xxx//Feign调用流程/*** 1、构造请求数据,将对象转为json;*      RequestTemplate template = buildTemplateFromArgs.create(argv);* 2、发送请求进行执行(执行成功会解码响应数据):*      executeAndDecode(template);* 3、执行请求会有重试机制*      while(true){*          try{*            executeAndDecode(template);*          }catch(){*              try{retryer.continueOrPropagate(e);}catch(){throw ex;}*              continue;*          }**      }*/}}

131 商城业务-商品上架-构造sku检索属性

略。。

132 商城业务-商品上架-远程查询库存&泛型结果封装

package com.atguigu.common.utils;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.apache.http.HttpStatus;import java.util.HashMap;
import java.util.Map;/*** 返回数据** @author Mark sunlightcs@gmail.com*/
public class R extends HashMap<String, Object> {private static final long serialVersionUID = 1L;//利用fastjson进行逆转public <T> T getData(String key,TypeReference<T> typeReference){Object data = get(key);//默认是mapString s = JSON.toJSONString(data);T t = JSON.parseObject(s, typeReference);return t;}//利用fastjson进行逆转public <T> T getData(TypeReference<T> typeReference){Object data = get("data");//默认是mapString s = JSON.toJSONString(data);T t = JSON.parseObject(s, typeReference);return t;}public R setData(Object data){put("data",data);return this;}public R() {put("code", 0);put("msg", "success");}public static R error() {return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知异常,请联系管理员");}public static R error(String msg) {return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg);}public static R error(int code, String msg) {R r = new R();r.put("code", code);r.put("msg", msg);return r;}public static R ok(String msg) {R r = new R();r.put("msg", msg);return r;}public static R ok(Map<String, Object> map) {R r = new R();r.putAll(map);return r;}public static R ok() {return new R();}public R put(String key, Object value) {super.put(key, value);return this;}public  Integer getCode() {return (Integer) this.get("code");}}

133 商城业务-商品上架-远程上架接口

package com.atguigu.gulimall.search.controller;import com.atguigu.common.exception.BizCodeEnume;
import com.atguigu.common.to.es.SkuEsModel;
import com.atguigu.common.utils.R;
import com.atguigu.gulimall.search.service.ProductSaveService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;/*** @创建人: 放生* @创建时间: 2022/4/24* @描述:*/
@Slf4j
@RequestMapping("/search/save")
@RestController
public class ElasticSaveController {@AutowiredProductSaveService productSaveService;//上架商品@PostMapping("/product")public R productStatusUp(@RequestBody List<SkuEsModel> skuEsModels) {boolean b = false;try {b = productSaveService.productStatusUp(skuEsModels);} catch (Exception e) {log.error("ElasticSaveController商品上架错误:{}", e);return R.error(BizCodeEnume.PRODUCT_UP_EXCEPTION.getCode(), BizCodeEnume.PRODUCT_UP_EXCEPTION.getMsg());}if (!b) {return R.ok();} else {return R.error(BizCodeEnume.PRODUCT_UP_EXCEPTION.getCode(), BizCodeEnume.PRODUCT_UP_EXCEPTION.getMsg());}}}
/*** @创建人: 放生* @创建时间: 2022/4/24* @描述:*/
public interface ProductSaveService {boolean productStatusUp(List<SkuEsModel> skuEsModels) throws IOException;
}
package com.atguigu.gulimall.search.service.impl;import com.alibaba.fastjson.JSON;
import com.atguigu.common.to.es.SkuEsModel;
import com.atguigu.gulimall.search.config.GulimallElasticSearchConfig;
import com.atguigu.gulimall.search.constant.EsConstant;
import com.atguigu.gulimall.search.service.ProductSaveService;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;/*** @创建人: 放生* @创建时间: 2022/4/24* @描述:*/
@Slf4j
@Service
public class ProductSaveServiceImpl implements ProductSaveService {@AutowiredRestHighLevelClient restHighLevelClient;@Overridepublic boolean productStatusUp(List<SkuEsModel> skuEsModels) throws IOException {//保存到es//1、给es中建立索引。product,建立好映射关系。//2、给es中保存这些数据//BulkRequest bulkRequest, RequestOptions optionsBulkRequest bulkRequest = new BulkRequest();for (SkuEsModel model : skuEsModels) {//1、构造保存请求IndexRequest indexRequest = new IndexRequest(EsConstant.PRODUCT_INDEX);indexRequest.id(model.getSkuId().toString());String s = JSON.toJSONString(model);indexRequest.source(s, XContentType.JSON);bulkRequest.add(indexRequest);}BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);//TODO 1、如果批量错误boolean b = bulk.hasFailures();List<String> collect = Arrays.stream(bulk.getItems()).map(item -> {return item.getId();}).collect(Collectors.toList());log.info("商品上架完成:{},返回数据:{}",collect,bulk.toString());return b;}}

134 商城业务-商品上架-上架接口调试&feign源码

                         //远程调用失败//TODO 7、重复调用?接口幂等性;重试机制?xxx//Feign调用流程/*** 1、构造请求数据,将对象转为json;*      RequestTemplate template = buildTemplateFromArgs.create(argv);* 2、发送请求进行执行(执行成功会解码响应数据):*      executeAndDecode(template);* 3、执行请求会有重试机制*      while(true){*          try{*            executeAndDecode(template);*          }catch(){*              try{retryer.continueOrPropagate(e);}catch(){throw ex;}*              continue;*          }**      }*/

135 商城业务-商品上架-抽取响应结果&上架测试完成

略 。。。

136 商城业务-首页-整合thymeleaf渲染首页

前面所有的操作都是采用前后端分离的,接下来我们要整合thymeleaf做成单独的服务,因为每一个模块都是有自己的数据,现在整合上thymeleaf就能单独的部署了。

1、引入依赖

在product的模块引入thymeleaf的依赖

      <!--   模板引擎: thymeleaf --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>

2、cope资料

将资料中的页面前端index的包cope到static包下,把index.htmlcope到template目录下

3、yaml配置

添加thymeleaf的相关配置

spring:  thymeleaf:cache: falsesuffix: .html  # 默认配置可以不配置prefix: classpath:/templates/ # 默认配置可以不配置

4、在product包下新建web包

web包用于存放所有的页面跳转的,整合thymeleaf的controller

5、测试

启动product服务 访问 http://localhost:10000/

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-13SwfTKI-1652453482293)(https://gitee.com/jiushuli/images/raw/master/image-20220428130358806.png)]

5、模板引擎*  1)、thymeleaf-starter:关闭缓存*  2)、静态资源都放在static文件夹下就可以按照路径直接访问*  3)、页面放在templates下,直接访问*      SpringBoot,访问项目的时候,默认会找index*  4)、页面修改不重启服务器实时更新*      1)、引入dev-tools*      2)、修改完页面 controller shift f9重新自动编译下页面,代码配置,推荐重启

137 商城业务-首页-整合dev-tools渲染一级分类数据

要实现不管访问的是localhost:10000 还是 localhost:10000/index都是访问的是首页,

1、先加上热启动的功能

加上依赖后 需要启动的话 ctrl + F9 即可编译

             <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency>

2、获取一级菜单的controller

package com.atguigu.gulimall.product.web;import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;import java.util.List;/*** @创建人: 放生* @创建时间: 2022/4/28* @描述:*/
@Controller
public class IndexController {@AutowiredCategoryService categoryService;@GetMapping({"/","/index.html"})public String indexPage(Model model){System.out.println(""+Thread.currentThread().getId());//TODO 1、查出所有的1级分类List<CategoryEntity> categoryEntities =  categoryService.getLevel1Categorys();// 视图解析器进行拼串:// classpath:/templates/ +返回值+  .htmlmodel.addAttribute("categorys",categoryEntities);return "index";}}

3、CategoryServiceImpl

    @Overridepublic List<CategoryEntity> getLevel1Categorys() {System.out.println("getLevel1Categorys.....");long l = System.currentTimeMillis();List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));return categoryEntities;}

4、页面渲染index.html

先引入thymeleaf的标签

<html lang="en" xmlns:th="http://www.thymeleaf.org">
    <!--轮播主体内容--><div class="header_main"><div class="header_banner"><div class="header_main_left"><ul><li th:each="category : ${categorys}"><a href="/static/#" class="header_main_left_a" th:attr="ctg-data=${category.catId}"><bth:text="${category.name}">家用电器</b></a></li></ul>..........

5、测试即可

138 商城业务-首页-渲染二级三级分类数据

在左侧显示一级菜单,鼠标放在一级上才显示对应的二三级菜单 。之前所有的数据是当鼠标放在一级菜单上是去catalogLoader.js调用的index/json/catalog.json中的数据,所以我们按照index/json/catalog.json的数据格式来写一个我们自己的接口

1、写个Catelog2Vo

Catelog2Vo 的数据格式就是参照index/json/catalog.json

package com.atguigu.gulimall.product.vo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.List;/*** @创建人: 放生* @创建时间: 2022/4/28* @描述:*/
//2级分类vo
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Catelog2Vo {private String catalog1Id; //1级父分类idprivate List<Catelog3Vo> catalog3List;  //三级子分类private String id;private String name;/**** 三级分类vo*  "catalog2Id":"1",*                     "id":"1",*                     "name":"电子书"*/@NoArgsConstructor@AllArgsConstructor@Datapublic static class Catelog3Vo{private String catalog2Id;//父分类,2级分类idprivate String id;private String name;}}

2、IndexController

//index/catalog.json@ResponseBody@GetMapping("/index/catalog.json")public Map<String, List<Catelog2Vo>> getCatalogJson(){Map<String, List<Catelog2Vo>> catalogJson = categoryService.getCatalogJson();return catalogJson;}

3、CategoryServiceImpl

@Overridepublic Map<String, List<Catelog2Vo>> getCatalogJson() {System.out.println("查询了数据库.....");List<CategoryEntity> selectList = baseMapper.selectList(null);List<CategoryEntity> level1Categorys = getParent_cid(selectList, 0L);//2、封装数据Map<String, List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {//1、每一个的一级分类,查到这个一级分类的二级分类List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());//2、封装上面面的结果List<Catelog2Vo> catelog2Vos = null;if (categoryEntities != null) {catelog2Vos = categoryEntities.stream().map(l2 -> {Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());//1、找当前二级分类的三级分类封装成voList<CategoryEntity> level3Catelog = getParent_cid(selectList, l2.getCatId());if (level3Catelog != null) {List<Catelog2Vo.Catelog3Vo> collect = level3Catelog.stream().map(l3 -> {//2、封装成指定格式Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());return catelog3Vo;}).collect(Collectors.toList());catelog2Vo.setCatalog3List(collect);}return catelog2Vo;}).collect(Collectors.toList());}return catelog2Vos;}));return parent_cid;}private List<CategoryEntity> getParent_cid(List<CategoryEntity> selectList, Long parent_cid) {List<CategoryEntity> collect = selectList.stream().filter(item -> item.getParentCid() == parent_cid).collect(Collectors.toList());//return baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", v.getCatId()));return collect;}

4、catalogLoader.js中的请求路径改成我们的url

$(function(){$.getJSON("index/catalog.json",function (data) {var ctgall=data;$(".header_main_left_a").each(function(){var ctgnums= $(this).attr("ctg-data")......

5、测试即可

谷粒商城-07-p102-p138相关推荐

  1. 谷粒商城--订单服务--高级篇笔记十一

    1.页面环境搭建 1.1 静态资源导入nginx 等待付款 --------->detail 订单页 --------->list 结算页 --------->confirm 收银页 ...

  2. 谷粒商城高级篇笔记1

    这里写自定义目录标题 0.ElasticSearch 1.Nginx配置域名问题 01.Nginx(反向代理) 配置 02.Nginx(负载均衡)+ 网关 配置 03.Nginx动静分离 2.JMet ...

  3. Java常用类(谷粒商城学习记录)

    Java常用类 谷粒商城学习记录 谷粒商城学习记录 谷粒商城学习记录 干嘛老是提示我与别人的文章相似呢?真的是我自己整理的啊啊啊 老是提示与这个文章相似 https://blog.csdn.net/c ...

  4. 谷粒商城之高级篇(3)

    2 商城业务 2.7 订单服务 2.7.22 创建业务交换机&队列 这里承接 知识补充篇 6 RabbitMQ 订单分布式主体逻辑 订单超时未支付触发订单过期状态修改与库存解锁 创建订单时消息 ...

  5. 【谷粒商城 -秒杀服务】

    谷粒商城–秒杀服务–高级篇笔记十二 1.后台添加秒杀商品 未配置秒杀服务相关网关 1.1 配置网关 - id: coupon_routeuri: lb://gulimall-couponpredica ...

  6. 谷粒商城--秒杀服务--高级篇笔记十二

    谷粒商城–秒杀服务–高级篇笔记十二 1.后台添加秒杀商品 未配置秒杀服务相关网关 1.1 配置网关 - id: coupon_routeuri: lb://gulimall-couponpredica ...

  7. 谷粒商城学习笔记——第一期:项目简介

    一.项目简介 1. 项目背景 市面上有5种常见的电商模式 B2B.B2C.C2B.C2C.O2O B2B 模式(Business to Business),是指商家和商家建立的商业关系.如阿里巴巴 B ...

  8. 谷粒商城高级篇资料_一文搞定剑指offer面试题【分文别类篇】

    点击上方"蓝字",关注了解更多 数组: 面试题3:数组中重复的数字 面试题4:二维数组中的查找 面试题21:调整数组顺序使奇数位于偶数前面 面试题39:数组中出现次数超过一半的数字 ...

  9. 谷粒商城RabbitMQ锁库存逻辑详解--新理解(长文警告)

    前言 不废话,上来就说,代码我会放挺多,写过这个项目的自然能懂,如果真的像理解的请认真看哦 分析 /*出现的问题:扣减库存成功了,但是由于网络原因超时,出现异常,导致订单事务回滚,库存事务不回滚(解决 ...

  10. gulimall(谷粒商城) 是一个综合性的B2C平台,包括前台商城系统以及后台管理系统

    介绍: 项目介绍 gulimall(谷粒商城) 项目是一套电商项目,包括前台商城系统以及后台管理系统,基于 SpringCloud + SpringCloudAlibaba + MyBatis-Plu ...

最新文章

  1. SQL Server基础知识之:设计和实现视图
  2. 二阶声波正演c语言程序_嵌入式开发中的三种程序构架
  3. 测验6: 组合数据类型 (第6周)
  4. c++ new[] delete[]底层分析
  5. 运放搭建主动滤波电路
  6. linux 管道文件上机总结,[转载]LINUX 管道 fifo 等总结
  7. [代码]Delphi实现双击左CTRL键调用记事本
  8. Linux查看分区文件系统类型总结
  9. 20140708testC
  10. wireshark使用方法总结
  11. 阿里张勇《人民日报》刊发署名文章:抓住数字新基建的机遇
  12. JSON.stringify的认知历程
  13. AT&T拟利用电力线网络提供无线宽带服务
  14. 做站源码下载地址及各类资源站点
  15. Android Banner图片轮播
  16. windows 搭建eureka注册中心
  17. uniapp中使用svga动画
  18. IT从业人员面试经典70问答
  19. Hash-based Shuffle内幕彻底解密
  20. 2021Y非理性繁荣

热门文章

  1. java list 查询_Java List 快速搜索对象
  2. 从键盘输入直角三角形两条直角边的长度,求斜边的长度和三角形的面积,计算结果保留两位小数。
  3. Unity3D简单电影视图编辑
  4. 人最宝贵的东西是生命
  5. 211高校取消138名研究生学位申请资格!研究生论文“盲审”“查重”再升级!...
  6. Linux有几种系列的发行版本?
  7. 美国访问学者J1签证申请攻略
  8. ORACLE 数据文件创建,查询,删除,恢复 相关SQL 整理
  9. 保持忠贞是不容易的,需要持续付出努力
  10. 90.网络安全渗透测试—[常规漏洞挖掘与利用篇6]—[文件包含-PHP封装伪协议详解实战示例]