? 知识点概览
为了方便后续回顾该项目时能够清晰的知道本章节讲了哪些内容,并且能够从该章节的笔记中得到一些帮助,所以在完成本章节的学习后在此对本章节所涉及到的知识点进行总结概述。
本章节为【学成在线】项目的 day10
的内容
- 课程发布功能开发
-
ElasticsSearch
安装部署 -
ElasticsSearch
快速入门、IK
分词器、映射、索引。一、课程发布
0x01 需求分析
课程发布后将生成正式的课程详情页面,课程发布后用户即可浏览课程详情页面,并开始课程的学习。课程发布生成课程详情页面的流程与课程预览业务流程相同,如下:
1、用户进入教学管理中心,进入某个课程的管理界面
2、点击课程发布,前端请求到课程管理服务
3、课程管理服务远程调用 CMS
生成课程发布页面,CMS
将课程详情页面发布到服务器
4、课程管理服务修改课程发布状态为 “已发布”,并向前端返回发布成功
5、用户在教学管理中心点击 “课程详情页面” 链接,查看课程详情页面内容
流程图如下
0x02 CMS一键发布接口
1、需求分析
根据需求分析内容,需要在 cms
服务增加页面发布接口供课程管理服务调用,此接口的功能如下:
1、接收课程管理服务发布的页面信息
2、将页面信息添加到 数据库**(mongodb)**
3、对页面信息进行静态化
4、将页面信息发布到服务器
2、接口定义
1、创建响应结果类型
页面发布成功cms返回页面的 url
页面 Url
= cmsSite.siteDomain
cmsSite.siteWebPath
cmsPage.pageWebPath
cmsPage.pageName
@Data
@NoArgsConstructor
public class CmsPostPageResult extends ResponseResult {
String pageUrl;
public CmsPostPageResult(ResultCode resultCode,String pageUrl){
super(resultCode);
this.pageUrl = pageUrl;
}
}
2、在 api
工程定义页面发布接口
/**
* 一键发布页面
*/
@ApiOperation("一键发布页面")
public CmsPostPageResult postPageQuick(CmsPage cmsPage);
3、Dao
代码语言:javascript复制/**
* 继承MongoDB自带的一些Repository
*/
public interface CmsSiteRepository extends MongoRepository<CmsSite,String{
}
4、Service
代码语言:javascript复制/**
* 一键发布页面
* @param cmsPage
* @return
*/
public CmsPostPageResult postPageQuick(CmsPage cmsPage) {
//添加页面,这里直接调用我们在做预览页面时候开发的保存页面方法
CmsPageResult cmsPageResult = this.saveCmsPage(cmsPage);
if(!cmsPageResult.isSuccess()){
//添加页面失败
return new CmsPostPageResult(CommonCode.FAIL,null);
}
CmsPage saveCmsPage = cmsPageResult.getCmsPage();
String pageId = saveCmsPage.getPageId();
//发布页面,通知cms client发布页面
ResponseResult responseResult = this.postPage(pageId);
if(!responseResult.isSuccess()){
//发布失败
return new CmsPostPageResult(CommonCode.FAIL,null);
}
//得到页面的url,页面url=站点域名 站点webpath 页面webpath 页面名称
//所以这里我们需要获取站点信息
String siteId = saveCmsPage.getSiteId();
Optional<CmsSite> cmsSiteOptional = cmsSiteRepository.findById(siteId);
if(!cmsSiteOptional.isPresent()){
//获取站点异常
return new CmsPostPageResult(CommonCode.FAIL,null);
}
CmsSite cmsSite = cmsSiteOptional.get();
//站点和页面的信息
String siteDomain = cmsSite.getSiteDomain();
String siteWebPath = cmsSite.getSiteWebPath();
String pageWebPath = saveCmsPage.getPageWebPath();
String pageName = saveCmsPage.getPageName();
//发布页面的访问地址
String pageUrl = siteDomain siteWebPath pageWebPath pageName;
return new CmsPostPageResult(CommonCode.SUCCESS, pageUrl);
}
2、页面发布方法
5、Controller
代码语言:javascript复制/**
* 一键发布页面
* @param cmsPage
* @return
*/
@Override
@PostMapping("/postPageQuick")
public CmsPostPageResult postPageQuick(@RequestBody CmsPage cmsPage) {
return pageService.postPageQuick(cmsPage);
}
0x03 课程发布接口
1、API接口
代码语言:javascript复制@ApiOperation("课程发布")
public CoursePublishResult CoursePublish(String courseId);
2、创建Feign Client
定义一个远程调用的方法,用于远程调用刚才我们在 CMS 服务中定义的一键发布接口。
代码语言:javascript复制@FeignClient(value = "XC-SERVICE-MANAGE-CMS")
public interface CmsPageClient {
@PostMapping("/cms/page/postPageQuick")
CmsPostPageResult postPageQuick(@RequestBody CmsPage cmsPage);
}
3、Service
1、配置课程发布页面参数
新增课程详情页面的站点信息,如果已增加课程详情页面的站点则忽略此步骤。
向 mongodb
的 cms_site
中新增如下信息
{
"_id" : ObjectId("5b30b052f58b4411fc6cb1cf"),
"_class" : "com.xuecheng.framework.domain.cms.CmsSite",
"siteName" : "课程详情站点",
"siteDomain" : "http://www.xuecheng.com",
"sitePort" : "80",
"siteWebPath" : "/",
"siteCreateTime" : ISODate("2018-02-03T02:34:19.113 0000")
}
在 application.yml
中配置
course-publish:
siteId: 5b30b052f58b4411fc6cb1cf
templateId: 5e93d2e3d79e7d6ed1009b95
previewUrl: http://www.xuecheng.com/cms/preview/
pageWebPath: /course/detail/
pagePhysicalPath: G:/job/code/Project/XueChengOnline/xcEduUI01/xuecheng/static/course/detail/
dataUrlPre: http://localhost:31200/course/preview/model/
-
siteId
:站点id
-
templateId
:模板id
-
dataurlPre
:数据url
的前缀 -
pageWebPath
: 页面的web
访问路径 -
pagePhysicalPath
: 这个指的是页面要发布到nginx
服务器的物理路径,我们在配置文件中定义,可以动态调整,便于扩展。
2、service 方法如下
代码语言:javascript复制//拼装页面信息
private CmsPage setPageInfo(String courseId){
//获取课程信息
CourseBase courseBaseById = this.findCourseBaseById(courseId);
//拼装页面基本信息
CmsPage cmsPage = new CmsPage();
cmsPage.setDataUrl(publish_dataUrlPre courseId);
cmsPage.setPagePhysicalPath(publish_page_physicalpath);
cmsPage.setPageWebPath(publish_page_webpath);
cmsPage.setSiteId(publish_siteId);
cmsPage.setTemplateId(publish_templateId);
cmsPage.setPageName(courseId ".html");
//页面别名
cmsPage.setPageAliase(courseBaseById.getName());
return cmsPage;
}
/**
* 课程详细页面发布
*/
@Transactional
public CoursePublishResult coursePublish(String courseId){
//拼装页面信息
CmsPage cmsPage = setPageInfo(courseId);
//发布课程详细页面
CmsPostPageResult cmsPostPageResult = cmsPageClient.postPageQuick(cmsPage);
if(!cmsPostPageResult.isSuccess()){
return new CoursePublishResult(CommonCode.FAIL,null);
}
//更新课程状态
CourseBase courseBaseById = this.findCourseBaseById(courseId);
courseBaseById.setStatus("202002");
courseBaseRepository.save(courseBaseById);
//课程索引...
//课程缓存...
//页面url
String pageUrl = cmsPostPageResult.getPageUrl();
return new CoursePublishResult(CommonCode.SUCCESS,pageUrl);
}
4、Controller
代码语言:javascript复制/**
* 课程发布
* @param courseId
* @return
*/
@Override
@PostMapping("/publish/{id}")
public CoursePublishResult CoursePublish(@PathVariable("id") String courseId) {
return courseService.coursePublish(courseId);
}
5、接口测试
0x04 测试CMS一键发布接口
测试前准备工作
1、启动RabbitMQ
服务
2、启动 cms
、course
服务
3、启动 cms_client
,注意配置 routingKey
和队列名称,routingKey
为所使用的站点id
4、这里我们分别在 course
、cms
、cms client 三个服务内对应的地方打上断点,观察发布的流程
course
服务断点,我们设置在远程调用 cms 一键发布接口返回结果之后
cms 服务断点,在 一键发布接口 开始执行的地方我们打上断点
当 cms 客户端接收到 cms 通过 rabbitMQ 发送的消息后,会调用该方法将页面保存到 nginx 服务器的物理路径内
准备工作做完了,我们在 course 服务生成的 sawgger-ui 进行测试
发送请求后,我们在 idea 中可以看到,断点已经跑到了 cms 服务的一键发布接口
继续执行的话,cms
服务会调用 this.postPage
方法,获取该页面的 数据模型、以及模板数据,将数据静态化后得到完整的页面数据,并且写入到 GridFS
中;
写入 GridFS
后通过 rabbitMQ
发送消息到 cms client
,消息的内容包含一个页面id
,入下图,断点已经走到了 cms client
服务的 savePageToServerPath
方法中
``cms client接收到
pageId后,获取该页面数据的 fileId,通过该id到
GridFS` 中找到该页面的数据,并写入到页面的物理路径内,写入完成后,回到 cms 服务内拼装发布后的页面地址,再返回到 course 服务
course 服务接收到 cms 的响应,取出 pageUrl 返回到前端,如下图
回到 swagger ui,我们可以看到已经返回了一个发布成功的url
访问页面看下效果
0x05 前端页面开发与测试
1、页面开发
代码语言:javascript复制<el-card class="box-card">
<div slot="header" class="clearfix">
<span>课程发布</span>
</div>
<div class="text item">
<div v-if="course.status == '202001'">
状态:制作中<br/>
<el-button type="primary" @click.native="publish" >新课程发布</el-button>
</div>
<div v-else-if="course.status == '202003'">
状态:已下线
<br/><br/>
<span><a :href="'http://www.xuecheng.com/course/detail/' this.courseid '.html'" target="_blank">点我查看课程详情页面 </a> </span>
</div>
<div v-else-if="course.status == '202002'">
状态:已发布<br/>
<el-button type="primary" @click.native="publish" >修改发布</el-button>
<br/><br/>
<span><a :href="'http://www.xuecheng.com/course/detail/' this.courseid '.html'" target="_blank">点我查看课程详情页面 </a> </span>
</div>
</div>
</el-card>
2、获取课程状态
代码语言:javascript复制getCourseView(){
courseApi.findCourseView(this.courseid).then(res=>{
if(res){
//获取课程状态
this.course.status = res.status;
}
})
}
在页面初始化完成其前执行
代码语言:javascript复制mounted(){
//课程id
this.courseid = this.$route.params.courseid;
//查询课程信息
this.getCourseView();
}
3、页面发布接口
api接口定义
代码语言:javascript复制/*发布课程*/
export const publish = id => {
return http.requestPost(apiUrl '/course/publish/' id);
}
api接口实现
代码语言:javascript复制publish(){
//课程发布
courseApi.publish(this.courseid).then(res=>{
if(res.success){
this.$message.success("发布成功,请点击下边的链接查询课程详情页面")
this.getCourseView();
}else{
this.$message.error(res.message)
}
})
},
测试
二、初识 Elastic Search
0x01 是什么?为什么?
1、简历
百度百科
官方网址:https://www.elastic.co/cn/products/elasticsearch
Github :https://github.com/elastic/elasticsearch
总结:
1、elasticsearch
是一个基于Lucene
的高扩展的分布式搜索服务器,支持开箱即用。
2、elasticsearch
隐藏了 Lucene
的复杂性,对外提供 Restful
接口来操作索引、搜索。
突出优点:
- 扩展性好,可部署上百台服务器集群,处理PB级数据。
- 近实时的去索引数据、搜索数据。
市面上的同类型的产品有 solr
,那么 es
和 solr
选择哪个?
如果你公司现在用的 solr
可以满足需求就不要换了。
如果你公司准备进行全文检索项目的开发,建议优先考虑 elasticsearch
,因为像 Github
这样大规模的搜索都在用它。
2、原理于应用
索引结构
下图是 ElasticSearch
的索引结构,下边黑色部分是物理结构,上边黄色部分是逻辑结构,逻辑结构也是为了更好的去描述 ElasticSearch
的工作原理及去使用物理结构中的索引文件。
逻辑结构部分是一个倒排索引表:
1、将要搜索的文档内容分词,所有不重复的词组成分词列表。
2、将搜索的文档最终以 Document
方式存储起来。
3、每个词和 docment
都有关联。
如下:
现在,如果我们想搜索 quick brown
,我们只需要查找包含每个词条的文档:
两个文档都匹配,但是第一个文档比第二个匹配度更高。如果我们使用仅计算匹配词条数量的简单 相似性算法 ,那么,我们可以说,对于我们查询的相关性来讲,第一个文档比第二个文档更佳。
正排索引:根据词与文章内容的关系进行索引。 倒排索引:根据词与文章的关系进行索引,需要提前对词和文章进行关联。
3、如何使用es?
Elasticsearch
提供 RESTful Api
接口进行索引、搜索,并且支持多种客户端。
下图是es在项目中的应用方式:
1)用户在前端搜索关键字
2)项目前端通过 http
方式请求项目服务端
3)项目服务端通过 Http RESTful
方式请求 ES
集群进行搜索
4)ES
集群从索引库检索数据。
0x02 安装 Elastic Search
1、安装
安装配置:
1、新版本要求至少 jdk1.8
以上。
2、支持tar
、zip
、rpm
等多种安装方式。在 windows
下开发建议使用ZIP安装方式。
3、支持 docker
方式安装
详细参见:https://www.elastic.co/guide/en/elasticsearch/reference/current/install-elasticsearch.html
注意视频教程中用的是6.2.1的,为了适应较新的版本这里我这里下载 6.8.8
的版本作为演示
截至现在官方已经更新 到了 7.7.*,从语义化版本规范来看,6和7应该有架构上的重大变化,所以我还是选择6系的最新一个版本避免踩坑,以后有需求再去对比7的差异。
https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.8.8.zip
其他版本下载:https://www.elastic.co/cn/downloads/past-releases
下载完成后我们解压 elasticsearch-*.*.*.zip
bin
:脚本目录,包括:启动、停止等可执行脚本
config
:配置文件目录
data
:索引目录,存放索引文件的地方
logs
:日志目录
modules
:模块目录,包括了es的功能模块
plugins
:插件目录,es支持插件机制
2、配置文件
ES的配置文件的地址根据安装形式的不同而不同:
1、使用 zip
、tar
安装,配置文件的地址在安装目录的 config
下。
2、使用 RPM
安装,配置文件在 /etc/elasticsearch
下。
3、使用 MSI
安装,配置文件的地址在安装目录的 config
下,并且会自动将 config
目录地址写入环境变量
ES_PATH_CONF
。
本教程使用的 zip
包安装,配置文件在ES安装目录的config
下。
配置文件如下:
-
elasticsearch.yml
: 用于配置Elasticsearch运行参数 -
jvm.options
: 用于配置Elasticsearch JVM设置 -
log4j2.properties
: 用于配置Elasticsearch日志
elasticsearch.yml
配置格式是 YAML
,可以采用如下两种方式:
方式1:层次方式
代码语言:javascript复制path:
data: /var/lib/elasticsearch
logs: /var/log/elasticsearch
方式2:属性方式
代码语言:javascript复制path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch
本项目采用方式2,例子如下:
代码语言:javascript复制cluster.name: xuecheng
node.name: xc_node_1
network.host: 0.0.0.0
http.port: 9200
transport.tcp.port: 9300
node.master: true
node.data: true
#discovery.zen.ping.unicast.hosts: ["0.0.0.0:9300", "0.0.0.0:9301", "0.0.0.0:9302"]
discovery.zen.minimum_master_nodes: 1
bootstrap.memory_lock: false
node.max_local_storage_nodes: 1
path.data: D:ElasticSearchelasticsearch‐6.2.1data
path.logs: D:ElasticSearchelasticsearch‐6.2.1logs
http.cors.enabled: true
http.cors.allow‐origin: /.*/
注意 path.data
和 path.logs
路径配置正确。
常用的配置项如下:
配置字段 | 描述 |
---|---|
cluster.name | 配置 elasticsearch 的集群名称,默认是 elasticsearch。建议修改成一个有意义的名称。 |
node.name | 节点名,通常一台物理服务器就是一个节点,es 会默认随机指定一个名字,建议指定一个有意义的名称,方便管理一个或多个节点组成一个 cluster 集群,集群是一个逻辑的概念,节点是物理概念,后边章节会详细介绍。 |
path.conf | 设置配置文件的存储路径,tar或zip包安装默认在 es 根目录下的 config 文件夹,rpm 安装默认在 /etc/ |
path.logs | 设置日志文件的存储路径,默认是es根目录下的 logs 文件夹 |
path.plugins | 设置插件的存放路径,默认是 es 根目录下的 plugins 文件夹 |
bootstrap.memory_lock | true 设置为 true 可以锁住 ES 使用的内存,避免内存与 swap 分区交换数据。 |
elasticsearch path.data | 设置索引数据的存储路径,默认是 es 根目录下的 data 文件夹,可以设置多个存储路径,用逗号隔开。 path.logs: 设置日志文件的存储路径,默认是es根目录下的 logs 文件夹 |
network.host | 设置绑定主机的 ip 地址,设置为 0.0.0.0 表示绑定任何 ip,允许外网访问,生产环境建议设置为具体的 ip。 |
http.port | 9200 设置对外服务的 http 端口,默认为 9200。 |
transport.tcp.port | 9300 集群结点之间通信端口 |
node.master | 指定该节点是否有资格被选举成为 master 结点,默认是true,如果原来的master宕机会重新选举新的master。 |
node.data | 指定该节点是否存储索引数据,默认为 true。 |
discovery.zen.ping.unicast.hosts | ["host1:port", "host2:port", "..."] 设置集群中 master 节点的初始列表。 |
discovery.zen.ping.timeout | 3s 设置 ES 自动发现节点连接超时的时间,默认为3秒,如果网络延迟高可设置大些。 |
discovery.zen.minimum_master_nodes | 主结点数量的最少值 ,此值的公式为:(master_eligible_nodes / 2) 1 ,比如:有3个符合要求的主结点,那么这里要设置为2。 |
node.max_local_storage_nodes | 单机允许的最大存储结点数,通常单机启动一个结点建议设置为1,开发环境如果单机启动多个节点可设置大于1. |
jvm.options
设置最小及最大的JVM堆内存大小:
在 jvm.options
中设置 -Xms
和 -Xmx
:
1) 两个值设置为相等
2) 将 Xmx
设置为不超过物理内存的一半。
log4j2.properties
日志文件设置,ES使用 log4j
,注意日志级别的配置。
系统配置
在linux
上根据系统资源情况,可将每个进程最多允许打开的文件数设置大些。
su limit -n
查询当前文件数
使用命令设置 limit
:
先切换到 root
,设置完成再切回 elasticsearch
用户。
sudo su
ulimit ‐n 65536
su elasticsearch
也可通过下边的方式修改文件进行持久设置
/etc/security/limits.conf
将下边的行加入此文件:
elasticsearch ‐ nofile 65536
3、启动ES
进入 bin
目录,在 cmd
下运行:elasticsearch.bat
浏览器输入:http://localhost:9200
显示结果如下(配置不同内容则不同)说明 ES
启动成功:
{
"name" : "xc_node_1",
"cluster_name" : "xuecheng",
"cluster_uuid" : "J18wPybJREyx1kjOoH8T‐g",
"version" : {
"number" : "6.2.1",
"build_hash" : "7299dc3",
"build_date" : "2018‐02‐07T19:34:26.990113Z",
"build_snapshot" : false,
"lucene_version" : "7.2.1",
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}
4、安装 head 插件
head
插件是 ES
的一个可视化管理插件,用来监视ES的状态,并通过 head
客户端和 ES
服务进行交互,比如创建映射、创建索引等,head
的项目地址在 https://github.com/mobz/elasticsearch-head
从ES6.0 开始,head
插件支持使得 node.js
运行。
1、安装 node.js
2、下载 head 并运行
代码语言:javascript复制git clone git://github.com/mobz/elasticsearch-head.git
cd elasticsearch-head
npm install
npm run start open
访问 http://本地主机:9100/
3、运行
打开浏览器调试工具发现报错:
Origin null is not allowed by Access-Control-Allow-Origin.
原因是:head插件作为客户端要连接ES服务(localhost:9200),此时存在跨域问题,elasticsearch默认不允许跨域访问。
解决方案:
设置 elasticsearch
允许跨域访问
# 跨域配置
http.cors.enabled: true
http.cors.allow-origin: /.*/
跨域访问允许的域名地址,(允许所有域名) 以上使用正则 http.cors.allow-origin: /.*/
注意:将 config/elasticsearch.yml
另存为utf-8编码格式。
成功连接 ES
。
三、ES 快速入门
ES
作为一个索引及搜索服务,对外提供丰富的 REST
接口,快速入门部分的实例使用 head
插件来测试,目的是对 ES
的使用方法及流程有个初步的认识。
0x01 创建索引库
ES
的索引库是一个逻辑概念,它包括了分词列表及文档列表,同一个索引库中存储了相同类型的文档。它就相当于 MySQL
中的表,或相当于 Mongodb
中的集合。
关于索引这个语:
索引(名词):ES
是基于 Lucene
构建的一个搜索服务,它要从索引库搜索符合条件索引数据。
索引(动词):索引库刚创建起来是空的,将数据添加到索引库的过程称为索引。
下边介绍两种创建索引库的方法,它们的工作原理是相同的,都是客户端向 ES
服务发送命令。
1)使用 postman 或 curl 这样的工具创建:
put http://localhost:9200/xc_course
索引库名称
{
"settings":{
"index":{
"number_of_shards":1,
"number_of_replicas":0
}
}
}
number_of_shards:设置分片的数量,在集群中通常设置多个分片,表示一个索引库将拆分成多片分别存储不同的结点,提高了 ES
的处理能力和高可用性,入门程序使用单机环境,这里设置为 1。
number_of_replicas:设置副本的数量,设置副本是为了提高 ES
的高可靠性,单机环境设置为 0
.
如下是创建的例子,创建 xc_course
索引库,共 1
个分片,0
个副本:
使用 postman
发送 put 请求
2)使用head插件创建
效果如下
0x02 创建映射
1、概念说明
在索引中每个文档都包括了一个或多个 field
,创建映射就是向索引库中创建 field
的过程,下边是document
和 field
与关系数据库的概念的类比:
文档(Document)---------------- Row
记录
字段(Field)------------------- Columns
列
注意:6.0
之前的版本有 type
(类型)概念,type
相当于关系数据库的表,ES
官方将在 ES9.0
版本中彻底删除 type
。
上边讲的创建索引库相当于关系数据库中的数据库还是表?
1、如果相当于数据库就表示一个索引库可以创建很多不同类型的文档,这在 ES
中也是允许的。
2、如果相当于表就表示一个索引库只能存储相同类型的文档,ES
官方建议 在一个索引库中只存储相同类型的文档。
2、创建映射
我们要把课程信息存储到 ES
中,这里我们创建课程信息的映射,先来一个简单的映射,如下:
发送:post http://localhost:9200/索引库名称/类型名称/_mapping
创建类型为 xc_course
的映射,共包括三个字段:name
、description
、studymondel
由于 ES6.0
版本还没有将 type
彻底删除,所以暂时把 type
起一个没有特殊意义的名字。
发送 post 请求:http://localhost:9200/xc_course/doc/_mapping
在
xc_course
索引库下的doc
类型下创建映射。doc
是类型名,可以自定义,在ES6.0
中要弱化类型的概念,给它起一个没有具体业务意义的名称。
body
代码语言:javascript复制{
"properties": {
"name": {
"type": "text"
},
"description": {
"type": "text"
},
"studymodel": {
"type": "keyword"
}
}
}
映射创建成功,查看 head
界面:
0x03 创建文档
ES中的文档相当于MySQL数据库表中的记录。
发送:put 或 Post 到http://localhost:9200/xc_course/doc/id值
(如果不指定id值ES会自动生成ID)
http://localhost:9200/xc_course/doc/4028e58161bcf7f40161bcf8b77c0000
代码语言:javascript复制{
"name":"Bootstrap开发框架",
"description":"Bootstrap是由Twitter推出的一个前台页面开发框架,在行业之中使用较为广泛。此开发框架包含了大量的CSS、JS程序代码,可以帮助开发者(尤其是不擅长页面开发的程序人员)轻松的实现一个不受浏览器限制的精美界面效果。",
"studymodel":"201001"
}
使用 postman
测试:
通过 head
查询数据:
0x04 搜索文档
1、根据课程id查询文档 发送:get http://localhost:9200/xc_course/doc/4028e58161bcf7f40161bcf8b77c0000
使用 postman
测试:
2、查询所有记录
发送 get http://localhost:9200/xc_course/doc/_search
3、查询名称中包括 bootstrap
关键字的的记录
发送:get http://localhost:9200/xc_course/doc/_search?q=name:bootstrap
4、查询学习模式为 201001
的记录
发送 get http://localhost:9200/xc_course/doc/_search?q=studymodel:201001
查询结果分析
分析上边查询结果:
代码语言:javascript复制{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 0.2876821,
"hits": [
{
"_index": "xc_course",
"_type": "doc",
"_id": "4028e58161bcf7f40161bcf8b77c0000",
"_score": 0.2876821,
"_source": {
"name": "Bootstrap开发框架",
"description": "Bootstrap是由Twitter推出的一个前台页面开发框架,在行业之中使用较为广泛。此开发框架包含了大量的CSS、JS程序代码,可以帮助开发者(尤其是不擅长页面开发的程序人员)轻松的实现一个不受浏览器限制的精美界面效果。",
"studymodel": "201001"
}
}
]
}
}
字段名称 | 描述 |
---|---|
took | 本次操作花费的时间,单位为毫秒。 |
timed_out | 请求是否超时 |
_shards | 说明本次操作共搜索了哪些分片 |
hits | 搜索命中的记录 |
hits.total | 符合条件的文档总数 hits.hits :匹配度较高的前N个文档 |
hits.max_score | 文档匹配得分,这里为最高分 |
_score | 每个文档都有一个匹配度得分,按照降序排列。 |
_source | 显示了文档的原始内容。 |
四、IK分词器
0x01 测试ES默认的分词器
在添加文档时会进行分词,索引中存放的就是一个一个的词(term),当你去搜索时就是拿关键字去匹配词,最终找到词关联的文档。
测试当前索引库使用的分词器:
POST 请求:http://localhost:9200/_analyze
代码语言:javascript复制{"text":"分词器"}
结果如下:
代码语言:javascript复制{
"tokens": [
{
"token": "分",
"start_offset": 0,
"end_offset": 1,
"type": "<IDEOGRAPHIC>",
"position": 0
},
{
"token": "词",
"start_offset": 1,
"end_offset": 2,
"type": "<IDEOGRAPHIC>",
"position": 1
},
{
"token": "器",
"start_offset": 2,
"end_offset": 3,
"type": "<IDEOGRAPHIC>",
"position": 2
}
]
}
从响应的结果来看,ES
的默认分词器会将我们提交的内容中的每一个词进行分割,这样显然不够智能,下面我们来看一下 IK
分词器。
0x02 安装IK分词器
使用IK分词器可以实现对中文分词的效果。 下载IK分词器:(Github地址:https://github.com/medcl/elasticsearch-analysis-ik)
这里要注意的是,IK分词器的版本的ES的版本的相对应的,也就是说,如果我们 ES 的版本用的是 v6.8.8 那么IK分词器的版本也要对应使用 v6.8.8。
解压,并将解压的文件拷贝到 ES
安装目录的 plugins
下的ik目录下
测试分词效果:
POST请求: http://localhost:9200/_analyze
代码语言:javascript复制{"text":"测试分词器,后边是测试内容:springcloud实战","analyzer":"ik_max_word"}
请求结果
代码语言:javascript复制{
"tokens": [
{
"token": "测试",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 0
},
{
"token": "分词器",
"start_offset": 2,
"end_offset": 5,
"type": "CN_WORD",
"position": 1
},
{
"token": "分词",
"start_offset": 2,
"end_offset": 4,
"type": "CN_WORD",
"position": 2
},
//...省略一部分结果
]
}
从结果可以看出,在我们引入 IK 插件之后,分词器能识别出我们提交的内容中的词语,细心的老铁会注意到我们在 analyzer
字段中引入了 ik_max_word
,这是 IK
插件中的一个分词模式,下面我们来仔细介绍一下 IK
插件中的几种分词模式。
0x03 两种分词模式
ik分词器有两种分词模式:ik_max_word
和 ik_smart
模式,下面我们来测试这两种模式。
1、ik_max_word
会将文本做最细粒度的拆分,比如会将 “中华人民共和国人民大会堂 ” 拆分为“ 中华人民共和国、中华人民、中华、华人、人民共和国、人民、共和国、大会堂、大会、会堂等词语。
发送 post
请求: http://localhost:9200/_analyze
{"text":"中华人民共和国人民大会堂","analyzer":"ik_max_word"}
请求结果
代码语言:javascript复制{
"tokens": [
{
"token": "中华人民共和国",
"start_offset": 0,
"end_offset": 7,
"type": "CN_WORD",
"position": 0
},
{
"token": "中华人民",
"start_offset": 0,
"end_offset": 4,
"type": "CN_WORD",
"position": 1
},
{
"token": "中华",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 2
},
{
"token": "华人",
"start_offset": 1,
"end_offset": 3,
"type": "CN_WORD",
"position": 3
},
//省略部分结果...
]
}
2、ik_smart
会做最粗粒度的拆分,比如会将 “中华人民共和国人民大会堂” 拆分为 中华人民共和国、人民大会堂。
代码语言:javascript复制{"text":"中华人民共和国人民大会堂","analyzer":"ik_smart"}
请求结果
代码语言:javascript复制{
"tokens": [
{
"token": "中华人民共和国",
"start_offset": 0,
"end_offset": 7,
"type": "CN_WORD",
"position": 0
},
{
"token": "人民大会堂",
"start_offset": 7,
"end_offset": 12,
"type": "CN_WORD",
"position": 1
}
]
}
0x04 自定义词库
如果要让分词器支持一些专有词语,可以自定义词库。
iK
分词器自带一个 main.dic
的文件,此文件为词库文件。
我们在上边的目录中新建一个 my.dic
文件(注意文件格式为 utf-8
,不要选择utf-8 BOM
)
可以在其中自定义词汇
定义 my.dic
, 以每行为单位
桂北汇
编辑 config
中的配置文件 IKAnalyzer.cfg.xml
中配置 my.dic
<?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">my.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
在适自定义配置生效之前,我们先来看一下未自定义之前的效果
发送:post http://localhost:9200/_analyze
代码语言:javascript复制{"text":"桂北汇", "analyzer":"ik_max_word" }
代码语言:javascript复制{
"tokens": [
{
"token": "桂北",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 0
},
{
"token": "汇",
"start_offset": 2,
"end_offset": 3,
"type": "CN_CHAR",
"position": 1
}
]
}
可以看到,在自定义的分词未生效之前,ES还是将我们的提交的 “桂北汇” 拆分了,因为常用的词语组合。
我们重启 ES
,使刚才修改的自定义配置生效再次测试分词效果:
{
"tokens": [
{
"token": "桂北汇",
"start_offset": 0,
"end_offset": 3,
"type": "CN_WORD",
"position": 0
},
{
"token": "桂北",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 1
},
{
"token": "汇",
"start_offset": 2,
"end_offset": 3,
"type": "CN_CHAR",
"position": 2
}
]
}
从结果可以看出,我们在 my.dic
中添加的 “桂北汇” 分词被识别出来了,由于我们使用的是 ik_max_word
分词模式,所以 ES
的 IK
插件会进行更 细粒度 的识别。
五、映射
上边章节安装了 ik
分词器,如果在索引和搜索时去使用 ik
分词器呢?如何指定其它类型的 field
,比如日期类型、数值类型等。本章节学习各种映射类型及映射维护方法。
0x01 映射维护方法
1、查询所有索引的映射
GET 请求: http://localhost:9200/_mapping
2、创建映射
POST 请求:http://localhost:9200/xc_course/doc/_mapping
代码语言:javascript复制{
"properties": {
"name": {
"type": "text"
},
"description": {
"type": "text"
},
"studymodel": {
"type": "keyword"
}
}
}
这里要注意得是,映射只能 创建,不能删除,以及类型也不能修改,因为如果这个映射一旦创建后,你可能会关联了大量的数据,所以修改是不可取的。
0x02 常用映射类型
下图是ES6.2核心的字段类型如下:
1、text 文本字段
字符串包括 text
和 keyword
两种类型
analyzer 属性
通过 analyzer
属性指定分词器。
下边指定 name
的字段类型为 text
,使用 ik
分词器的 ik_max_word
分词模式。
"name": {
"type": "text",
"analyzer":"ik_max_word"
}
上边指定了 analyzer
是指在索引和搜索都使用 ik_max_word
,如果单独想定义搜索时使用的分词器则可以通过
search_analyzer
属性。对于 ik
分词器建议是索引时使用 ik_max_word
将搜索内容进行细粒度分词,搜索时使用 ik_smart
提高搜索精确性 。
"name": {
"type": "text",
"analyzer":"ik_max_word",
"search_analyzer":"ik_smart"
}
index 属性
通过 index
属性指定是否索引。
默认为 index=true
,即要进行索引,只有进行索引才可以从索引库搜索到。
但是也有一些内容不需要索引,比如:商品图片地址只被用来展示图片,不进行搜索图片,此时可以将 index
设置为 false
。删除索引,重新创建映射,将 pic
的 index
设置为 false
,尝试根据 pic
去搜索,结果搜索不到数据
"pic": {
"type": "text",
"index":false
}
store 属性
是否在 source
之外存储,每个文档索引后会在 ES
中保存一份原始文档,存放在 _source
中,一般情况下不需要设置 store
为 true
,因为在 source
中已经有一份原始文档了。
测试
删除 xc_course/doc
下的映射 ,发送 DELETE 请求到 http://10.1.1.168:9200/xc_course
创建,发送PUT请求 http://localhost:9200/xc_course
索引库名称
{
"settings":{
"index":{
"number_of_shards":1,
"number_of_replicas":0
}
}
}
创建新映射:POST http://localhost:9200/xc_course/doc/_mapping
代码语言:javascript复制{
"properties": {
"name": {
"type": "text",
"analyzer":"ik_max_word",
"search_analyzer":"ik_smart"
},
"description": {
"type": "text",
"analyzer":"ik_max_word",
"search_analyzer":"ik_smart"
},
"pic":{
"type":"text",
"index":false
},
"studymodel":{
"type":"text"
}
}
}
插入文档:
POST http://localhost:9200/xc_course/doc/4028e58161bcf7f40161bcf8b77c0000
代码语言:javascript复制{
"name":"Bootstrap开发框架",
"description":"Bootstrap是由Twitter推出的一个前台页面开发框架,在行业之中使用较为广泛。此开发框架包含了大量的CSS、JS程序代码,可以帮助开发者(尤其是不擅长页面开发的程序人员)轻松的实现一个不受浏览器限制的精美界面效果。",
"pic":"group1/M00/00/01/wKhlQFqO4MmAOP53AAAcwDwm6SU490.jpg",
"studymodel":"201002"
}
查询测试:
GET请求: http://localhost:9200/xc_course/_search?q=name:开发
查询结果:获取到 name
中包含 “开发” 的文档
GET请求: http://localhost:9200/xc_course/_search?q=description:开发
查询结果:获取到 description
中包含 “开发” 的文档
GET请求: http://localhost:9200/xc_course/_search?q=pic:group1/M00/00/01/wKhlQFqO4MmAOP53AAAcwDwm6SU490.jpg
查询结果:由于前面 pic
字段 设置了 index
属性为 false
GET请求: http://localhost:9200/xc_course/_search?q=studymodel:201002
查询结果: 由于没有为 studymodel
字段使用的是默认的分词器,默认分词器会将我们前面插入的 “201002
” 索引为一个词,所以需要全部匹配才能搜索到。
通过测试发现:name
和 description
都支持全文检索,pic
不可作为查询条件。
2、keyword 关键词字段
上边介绍的 text
文本字段在映射时要设置分词器,keyword
字段为关键字字段,通常搜索 keyword
是按照整体搜索,所以创建 keyword
字段的索引时是不进行分词的,比如:邮政编码、手机号码、身份证等。keyword
字段通常用于过虑、排序、聚合等。
测试
创建一个新的索引库进行测试,或者删除原来的,这里我们创建一个新的
1、创建索引库 xc_course2
PUT http://10.1.1.168:9200/xc_course2
代码语言:javascript复制{
"settings":{
"index":{
"number_of_shards":1,
"number_of_replicas":0
}
}
}
2、创建映射
这里我们将 name
和 studymodel
这两个字段的 type
设置为 keyword
POST http://10.1.1.168:9200/xc_course2/doc/_mapping
代码语言:javascript复制{
"properties": {
"name": {
"type": "keyword"
},
"description": {
"type": "text"
},
"studymodel": {
"type": "keyword"
}
}
}
3、创建文档
创建映射后,我们创建文档数据用于测试
POST http://10.1.1.168:9200/xc_course2/doc/4028e58161bcf7f40161bcf8b77c0000
代码语言:javascript复制{
"name": "java编程基础",
"description": "java语言是世界第一编程语言,在软件开发领域使用人数最多。",
"studymodel": "201001"
}
4、测试查询
GET http://localhost:9200/xc_course2/_search?q=name:java
查询结果如下
代码语言:javascript复制{
"took": 2,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 0,
"max_score": null,
"hits": []
}
}
name
是 keyword
类型,所以查询方式是精确查询,所以查询不到任何数据。
下面我们尝试一下精确的查询
GET http://10.1.1.168:9200/xc_course2/_search?q=name:java编程基础
查询结果如下
代码语言:javascript复制{
"took": 2,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 0.2876821,
"hits": [
{
"_index": "xc_course2",
"_type": "doc",
"_id": "4028e58161bcf7f40161bcf8b77c0000",
"_score": 0.2876821,
"_source": {
"name": "java编程基础",
"description": "java语言是世界第一编程语言,在软件开发领域使用人数最多。",
"studymodel": "201001"
}
}
]
}
}
查询成功。
3、date 日期类型
日期类型不用设置分词器,通常日期类型的字段用于排序。
format 属性
通过 format
设置日期格式
例子:
下边的设置允许 date
字段存储年月日时分秒、年月日及毫秒三种格式。
POST: http://10.1.1.168:9200/xc_course/doc/_mapping
代码语言:javascript复制{
"properties": {
"timestamp": {
"type": "date",
"format": "yyyy‐MM‐dd HH:mm:ss||yyyy‐MM‐dd"
}
}
}
插入文档:
POST: http://localhost:9200/xc_course/doc/00002
代码语言:javascript复制{
"name": "spring开发基础",
"description": "spring 在java领域非常流行,java程序员都在用。",
"studymodel": "201001",
"pic":"group1/M00/00/01/wKhlQFqO4MmAOP53AAAcwDwm6SU490.jpg",
"timestamp":"2018‐07‐04 18:28:58"
}
查询测试
GET http://10.1.1.168:9200/xc_course/_search?q=name:开发
4、数值类型
下边是ES支持的数值类型
1、尽量选择范围小的类型,提高搜索效率
2、对于浮点数尽量用 比例因子,比如一个价格字段,单位为元,我们将比例因子设置为 100
这在 ES
中会按 分 存储,映射如下
"price": {
"type": "scaled_float",
"scaling_factor": 100
},
由于比例因子为100,如果我们输入的价格是 23.45
则 ES
中会将 23.45
乘以 100
存储在ES中。
如果输入的价格是 23.456
,ES 会将 23.456
乘以 100
再取一个接近原始值的数,得出 2346
。
使用比例因子的好处是 整型比浮点型更易压缩,节省磁盘空间。
如果比例因子不适合,则从下表选择范围小的去用:
更新已有映射,并插入文档:
POST: http://localhost:9200/xc_course/doc/3
代码语言:javascript复制{
"name": "spring开发基础",
"description": "spring 在java领域非常流行,java程序员都在用。",
"studymodel": "201001",
"pic":"group1/M00/00/01/wKhlQFqO4MmAOP53AAAcwDwm6SU490.jpg",
"timestamp":"2018‐07‐04 18:28:58",
"price":38.6
}
5、综合例子
1、发送 DELETE 请求到 http://10.1.1.168:9200/xc_course
2、创建,发送PUT请求 http://localhost:9200/xc_course
索引库名称
{
"settings":{
"index":{
"number_of_shards":1,
"number_of_replicas":0
}
}
}
3、创建如下映射
post:http://localhost:9200/xc_course/doc/_mapping
代码语言:javascript复制{
"properties": {
"name": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"description": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"pic": {
"type": "text",
"index": false
},
"studymodel": {
"type": "text"
}
}
}
4、插入文档
POST: http://localhost:9200/xc_course/doc/1
代码语言:javascript复制{
"name": "Bootstrap开发",
"description": "Bootstrap是由Twitter推出的一个前台页面开发框架,是一个非常流行的开发框架,此框架集成了多种页面效果。此开发框架包含了大量的CSS、JS程序代码,可以帮助开发者(尤其是不擅长页面开发的程序人员)轻松的实现一个不受浏览器限制的精美界面效果。",
"studymodel": "201002",
"price": 38.6,
"timestamp": "2018-04-25 19:11:35",
"pic": "group1/M00/00/00/wKhlQFs6RCeAY0pHAA Jx5ZjNDEM428.jpg"
}
5、搜索测试
GET: http://10.1.1.168:9200/xc_course/doc/_search?q=name:开发
六、索引管理(Java API)
0x01 搭建工程
1、ES客户端简介
ES提供多种不同的客户端:
TransportClient
ES提供的传统客户端,官方计划8.0版本删除此客户端。
RestClient
RestClient
是官方推荐使用的,它包括两种:Java Low Level REST Client
和 Java High Level REST Client
。
ES在 6.0 之后提供 Java High Level REST Client
, 两种客户端官方更推荐使用 Java High Level REST Client
,不过当前它还处于完善中,有些功能还没有。
文章中准备采用 Java High Level REST Client
,如果它有不支持的功能,则使用 Java Low Level REST Client
。
添加依赖:
代码语言:javascript复制<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch‐rest‐high‐level‐client</artifactId>
<version>6.8.8</version>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>6.8.8</version>
</dependency>
2、创建搜索工程
创建搜索工程(maven工程):xc-service-search
,添加 RestHighLevelClient
依赖及junit
依赖。
1)完整依赖文件如下
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>xc-framework-parent</artifactId>
<groupId>com.xuecheng</groupId>
<version>1.0-SNAPSHOT</version>
<relativePath>../xc-framework-parent/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>xc-service-search</artifactId>
<dependencies>
<dependency>
<groupId>com.xuecheng</groupId>
<artifactId>xc-framework-model</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.xuecheng</groupId>
<artifactId>xc-framework-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.xuecheng</groupId>
<artifactId>xc-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>6.8.8</version>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>6.8.8</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
</project>
2)配置文件
application.yml
server:
port: ${port:40100}
spring:
application:
name: xc-search-service
xuecheng:
elasticsearch:
hostlist: ${eshostlist:127.0.0.1:9200} #多个结点中间用逗号分隔
3)配置类
创建 com.xuecheng.search.config
包在其下创建配置类
package com.xuecheng.search.config;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Administrator
* @version 1.0
**/
@Configuration
public class ElasticsearchConfig {
@Value("${xuecheng.elasticsearch.hostlist}")
private String hostlist;
/**
* restHighLevelClient
* @return
*/
@Bean
public RestHighLevelClient restHighLevelClient(){
//解析hostlist配置信息
String[] split = hostlist.split(",");
//创建HttpHost数组,其中存放es主机和端口的配置信息
HttpHost[] httpHostArray = new HttpHost[split.length];
for(int i=0;i<split.length;i ){
String item = split[i];
httpHostArray[i] = new HttpHost(item.split(":")[0], Integer.parseInt(item.split(":")[1]), "http");
}
//创建RestHighLevelClient客户端
return new RestHighLevelClient(RestClient.builder(httpHostArray));
}
/**
* 项目主要使用RestHighLevelClient,对于低级的客户端暂时不用
* @return
*/
@Bean
public RestClient restClient(){
//解析hostlist配置信息
String[] split = hostlist.split(",");
//创建HttpHost数组,其中存放es主机和端口的配置信息
HttpHost[] httpHostArray = new HttpHost[split.length];
for(int i=0;i<split.length;i ){
String item = split[i];
httpHostArray[i] = new HttpHost(item.split(":")[0], Integer.parseInt(item.split(":")[1]), "http");
}
return RestClient.builder(httpHostArray).build();
}
}
4、启动类
代码语言:javascript复制package com.xuecheng.search;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
/**
* @author Administrator
* @version 1.0
**/
@SpringBootApplication
@EntityScan("com.xuecheng.framework.domain.search")//扫描实体类
@ComponentScan(basePackages={"com.xuecheng.api"})//扫描接口
@ComponentScan(basePackages={"com.xuecheng.search"})//扫描本项目下的所有类
@ComponentScan(basePackages={"com.xuecheng.framework"})//扫描common下的所有类
public class SearchApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(SearchApplication.class, args);
}
}
0x02 创建索引库
1、API
创建索引:
PUT: http://localhost:9200/索引名称
代码语言:javascript复制{
"settings":{
"index":{
"number_of_shards":1, #分片的数量
"number_of_replicas":0 #副本数量
},
},
}
创建映射:
http://localhost:9200/索引库名称/类型名称/mapping
创建类型为 xc_course
的映射,共包括三个字段:name、description、studymodel
PUT: http://localhost:9200/xc_course/doc/_mapping
代码语言:javascript复制{
"properties": {
"name": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"description": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"studymodel": {
"type": "keyword"
},
"price": {
"type": "float"
},
"timestamp": {
"type": "date",
"format": "yyyy‐MM‐dd HH:mm:ss||yyyy‐MM‐dd||epoch_millis"
}
}
}
2、Java Client
代码语言:javascript复制package com.xuecheng.search;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.client.IndicesClient;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContent;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.IOException;
@SpringBootTest
@RunWith(SpringRunner.class)
public class TestIndex {
@Autowired
RestHighLevelClient restHighLevelClient;
@Autowired
RestClient restClient;
//创建索引库
@Test
public void testCreateIndex() throws IOException {
//创建索引请求对象
CreateIndexRequest createIndexRequest = new CreateIndexRequest("xc_course");
//设置索引参数
createIndexRequest.settings(Settings.builder()
.put("number_of_shards",1)
.put("number_of_replicas",0)
);
//设置索引库的映射
//这里的映射数据直接前面测试API的JSON字符串
createIndexRequest.mapping("doc","{n"
" "properties": {n"
" "name": {n"
" "type": "text",n"
" "analyzer": "ik_max_word",n"
" "search_analyzer": "ik_smart"n"
" },n"
" "description": {n"
" "type": "text",n"
" "analyzer": "ik_max_word",n"
" "search_analyzer": "ik_smart"n"
" },n"
" "studymodel": {n"
" "type": "keyword"n"
" },n"
" "price": {n"
" "type": "float"n"
" },n"
" "timestamp": {n"
" "type": "date",n"
" "format": "yyyy‐MM‐dd HH:mm:ss||yyyy‐MM‐dd||epoch_millis"n"
" }n"
" }n"
"}",XContentType.JSON);
//操作客户端
IndicesClient indices = restHighLevelClient.indices();
//创建响应对象
CreateIndexResponse createIndexResponse = indices.create(createIndexRequest);
//得到响应结果
boolean shardsAcknowledged = createIndexResponse.isShardsAcknowledged();
System.out.println(shardsAcknowledged);
}
}
运行后成功创建映射
0x03 添加文档
1、API
格式如下: PUT /{index}/{type}/{id} { "field": "value", ... }
如果不指定 id
,ES
会自动生成。
一个例子: put http://localhost:9200/xc_course/doc/3
代码语言:javascript复制{
"name":"spring cloud实战",
"description":"本课程主要从四个章节进行讲解: 1.微服务架构入门 2.spring cloud 基础入门 3.实战Spring
Boot 4.注册中心eureka。",
"studymodel":"201001"
"price":5.6
}
2、Java Client
代码语言:javascript复制/**
* 添加文档
*/
@Test
public void testAddDoc() throws IOException {
//准备json数据
Map<String, Object> jsonMap = new HashMap<>();
jsonMap.put("name", "spring cloud实战");
jsonMap.put("description", "本课程主要从四个章节进行讲解: 1.微服务架构入门 2.spring cloud 基础入门 3.实战Spring Boot 4.注册中心eureka。");
jsonMap.put("studymodel", "201001");
SimpleDateFormat dateFormat =new SimpleDateFormat("yyyy‐MM‐dd HH:mm:ss");
jsonMap.put("timestamp", dateFormat.format(new Date()));
jsonMap.put("price", 5.6f);
//索引请求对象
IndexRequest indexRequest = new IndexRequest("xc_course","doc");
//指定索引文档内容
indexRequest.source(jsonMap);
//索引响应对象
IndexResponse indexResponse = restHighLevelClient.index(indexRequest);
//获取响应结果
DocWriteResponse.Result result = indexResponse.getResult();
System.out.println(result);
}
0x04 查询文档
1、API
格式如下: GET /{index}/{type}/{id}
2、Java Client
代码语言:javascript复制/**
* 查询文档
*/
@Test
public void getDoc() throws IOException {
GetRequest getRequest = new GetRequest(
"xc_course",
"doc",
"eWb_kXEB39nW0xAzs9Pd"
);
GetResponse getResponse = restHighLevelClient.get(getRequest);
boolean exists = getResponse.isExists();
if(!exists){
System.out.println("文档不存在");
}else{
Map<String, Object> sourceAsMap = getResponse.getSourceAsMap();
System.out.println(sourceAsMap);
}
}
0x05 更新文档
1、API
ES
更新文档的顺序是:先检索到文档、将原来的文档标记为删除、创建新文档、删除旧文档,创建新文档就会重建索引。
通过请求 Url
有两种方法:
1)完全替换
POST:http://localhost:9200/xc_test/doc/3
代码语言:javascript复制{
"name":"spring cloud实战",
"description":"本课程主要从四个章节进行讲解: 1.微服务架构入门 2.spring cloud 基础入门 3.实战SpringBoot 4.注册中心eureka。",
"studymodel":"201001",
"price":5.6
}
2)局部更新
下边的例子是只更新 price
字段。
post: http://localhost:9200/xc_test/doc/3/_update
代码语言:javascript复制{
"doc":{"price":66.6}
}
2、Java Client
使用 Client Api
更新文档的方法同上边第二种局部更新方法。
可以指定文档的部分字段也可以指定完整的文档内容。
/**
* 更新文档
*/
@Test
public void updateDoc() throws IOException {
UpdateRequest updateRequest = new UpdateRequest("xc_course", "doc","eWb_kXEB39nW0xAzs9Pd");
Map<String, String> map = new HashMap<>();
map.put("name", "spring cloud alibaba");
updateRequest.doc(map);
UpdateResponse update = restHighLevelClient.update(updateRequest);
RestStatus status = update.status();
System.out.println(status);
}
0x06 删除文档
1、API
根据 id
删除,格式如下:
DELETE: /{index}/{type}/{id}
搜索匹配删除,将搜索出来的记录删除,格式如下:
POST: /{index}/{type}/_delete_by_query
下边是搜索条件例子:
代码语言:javascript复制{
"query":{
"term":{
"studymodel":"201001"
}
}
}
上边例子的搜索匹配删除会将 studymodel
为 201001
的记录全部删除。
2、Java Client
代码语言:javascript复制//根据id删除文档
@Test
public void testDelDoc() throws IOException {
//删除文档id
String id = "eqP_amQBKsGOdwJ4fHiC";
//删除索引请求对象
DeleteRequest deleteRequest = new DeleteRequest("xc_course","doc",id);
//响应对象
DeleteResponse deleteResponse = client.delete(deleteRequest);
//获取响应结果
DocWriteResponse.Result result = deleteResponse.getResult();
System.out.println(result);
}
搜索匹配删除还没有具体的 api
,可以采用先搜索出文档 id
,根据文档 id
删除。