谷粒商城-07-p102-p138
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 风格的 DSL(domain-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 静态资源导入nginx 等待付款 --------->detail 订单页 --------->list 结算页 --------->confirm 收银页 ...
- 谷粒商城高级篇笔记1
这里写自定义目录标题 0.ElasticSearch 1.Nginx配置域名问题 01.Nginx(反向代理) 配置 02.Nginx(负载均衡)+ 网关 配置 03.Nginx动静分离 2.JMet ...
- Java常用类(谷粒商城学习记录)
Java常用类 谷粒商城学习记录 谷粒商城学习记录 谷粒商城学习记录 干嘛老是提示我与别人的文章相似呢?真的是我自己整理的啊啊啊 老是提示与这个文章相似 https://blog.csdn.net/c ...
- 谷粒商城之高级篇(3)
2 商城业务 2.7 订单服务 2.7.22 创建业务交换机&队列 这里承接 知识补充篇 6 RabbitMQ 订单分布式主体逻辑 订单超时未支付触发订单过期状态修改与库存解锁 创建订单时消息 ...
- 【谷粒商城 -秒杀服务】
谷粒商城–秒杀服务–高级篇笔记十二 1.后台添加秒杀商品 未配置秒杀服务相关网关 1.1 配置网关 - id: coupon_routeuri: lb://gulimall-couponpredica ...
- 谷粒商城--秒杀服务--高级篇笔记十二
谷粒商城–秒杀服务–高级篇笔记十二 1.后台添加秒杀商品 未配置秒杀服务相关网关 1.1 配置网关 - id: coupon_routeuri: lb://gulimall-couponpredica ...
- 谷粒商城学习笔记——第一期:项目简介
一.项目简介 1. 项目背景 市面上有5种常见的电商模式 B2B.B2C.C2B.C2C.O2O B2B 模式(Business to Business),是指商家和商家建立的商业关系.如阿里巴巴 B ...
- 谷粒商城高级篇资料_一文搞定剑指offer面试题【分文别类篇】
点击上方"蓝字",关注了解更多 数组: 面试题3:数组中重复的数字 面试题4:二维数组中的查找 面试题21:调整数组顺序使奇数位于偶数前面 面试题39:数组中出现次数超过一半的数字 ...
- 谷粒商城RabbitMQ锁库存逻辑详解--新理解(长文警告)
前言 不废话,上来就说,代码我会放挺多,写过这个项目的自然能懂,如果真的像理解的请认真看哦 分析 /*出现的问题:扣减库存成功了,但是由于网络原因超时,出现异常,导致订单事务回滚,库存事务不回滚(解决 ...
- gulimall(谷粒商城) 是一个综合性的B2C平台,包括前台商城系统以及后台管理系统
介绍: 项目介绍 gulimall(谷粒商城) 项目是一套电商项目,包括前台商城系统以及后台管理系统,基于 SpringCloud + SpringCloudAlibaba + MyBatis-Plu ...
最新文章
- SQL Server基础知识之:设计和实现视图
- 二阶声波正演c语言程序_嵌入式开发中的三种程序构架
- 测验6: 组合数据类型 (第6周)
- c++ new[] delete[]底层分析
- 运放搭建主动滤波电路
- linux 管道文件上机总结,[转载]LINUX 管道 fifo 等总结
- [代码]Delphi实现双击左CTRL键调用记事本
- Linux查看分区文件系统类型总结
- 20140708testC
- wireshark使用方法总结
- 阿里张勇《人民日报》刊发署名文章:抓住数字新基建的机遇
- JSON.stringify的认知历程
- AT&T拟利用电力线网络提供无线宽带服务
- 做站源码下载地址及各类资源站点
- Android Banner图片轮播
- windows 搭建eureka注册中心
- uniapp中使用svga动画
- IT从业人员面试经典70问答
- Hash-based Shuffle内幕彻底解密
- 2021Y非理性繁荣
热门文章
- java list 查询_Java List 快速搜索对象
- 从键盘输入直角三角形两条直角边的长度,求斜边的长度和三角形的面积,计算结果保留两位小数。
- Unity3D简单电影视图编辑
- 人最宝贵的东西是生命
- 211高校取消138名研究生学位申请资格!研究生论文“盲审”“查重”再升级!...
- Linux有几种系列的发行版本?
- 美国访问学者J1签证申请攻略
- ORACLE 数据文件创建,查询,删除,恢复 相关SQL 整理
- 保持忠贞是不容易的,需要持续付出努力
- 90.网络安全渗透测试—[常规漏洞挖掘与利用篇6]—[文件包含-PHP封装伪协议详解实战示例]