同步操作将从 flatfish/Java-Review 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
ElasticSerach:搜索
以后需要使用搜索,使用ES
是一套信息检索的包,jar包,不包含搜索引擎系统!
包含的:索引结构! 读写索引的工具,排序,搜索规则...工具类
Lucene和ElasticSearch的关系:
ElasticSearch是基于Lucene做了一些封装和增强
ElasticSearch,简称ES。是一个开源的高拓展的分布式全文检索的搜索引擎,它可以近乎实时的存储、检索数据,本身拓展性很好。可以拓展到上百台服务器。目的是通过简单的RestFul API 来隐藏Lucene作为核心。
谁在使用 三剑客:ELK - ElasticSearch+logstash+kibana
ES
ES是一个实时分布式搜索引擎和分析引擎,它可以让以前所未有的速度处理大数据成功可能。
全文搜索、结构化搜索、分析
Solr
Solr 是Apache 下的一个顶级项目,采用Java开发,它是基于Lucene的全文搜索服务器
用POST的方法向Solr服务器发送一个面试Filed以及其内容的文档,Solr根据XML文档删除、增加、更新索引
向外提供类似于 WebService 的API接口
Lucene
Lucene是Apache基金会 4 Jakarta的一个子项目
ES和Solr 总结
声明:JDK1.8,最低要求,ES客户端,界面工具
Java开发,ES的版本和我们之后的对应的Java核心jar包版本需要对应
下载
官网:https://www.elastic.co/cn/downloads/elasticsearch
ElasticSearch: https://mirrors.huaweicloud.com/elasticsearch/?C=N&O=D
logstash: https://mirrors.huaweicloud.com/logstash/?C=N&O=D
kibana: https://mirrors.huaweicloud.com/kibana/?C=N&O=D
Linux和Windows都可以学习
ELK三剑客,解压即用
需要NodeJS和Python
Windows安装
localhost:9200
如上图安装可视化界面 es head 插件
启动
npm install
npm run start
跨域访问问题
修改配置文件
http.cors.enabled: true
http.cors.allow-origin: "*"
索引就当作是一个数据库
这个 head 就是当作数据展示工具 kibana 使用爽
了解 ELK
ELK是ElasticSearch、Logstash、Kibana三大开源框架首字母的简称,市面上也称为 Elastic Stack。
收集清晰数据=>搜索、存储
安装Kibana
Kibana是一个针对ElasticSearch的开源分析和展示可视化平台,用来搜索、查看、交互存储在ElasticSearch索引的数据,使用Kibana可以通过各种图表进行高级数据分析以及展示。Kibana让海量的数据更容易理解。
它操作简单,基于浏览器的用户界面可以快速创建仪表板(dashboard)实时显示ElasticSearch查询状态,设置Libana十分简单,无需编码或者额外的基础架构,几分钟内就可以完成Kibana安装并启动ElasticSearch索引检测
官网: https://www.elastic.co/cn/kibana
Kibana版本要和ES版本对应
启动测试
进入bin目录下进程执行 kibana.bat
访问网址测试:http://localhost:5601
开发工具(Post、Curl、Head、谷歌浏览器插件测试)
以后所有的操作都在这里进行编写
汉化!修改配置文件,修改之后重启即可,据说Hadoop的环境很难搭建
i18n.locale: "zh-CN"
概述
集群,节点,索引,类型,文档,分片,映射
ES是面向文档。关系型数据库和ES的对比 一切都是JSON
关系型数据库 | ElasticSearch |
---|---|
数据库(Database) | 索引(indices) |
表(tables) | types(慢慢被弃用) |
行(rows) | documents |
字段(columns) | fields |
ES集群中可以包含多个索引(数据库),每个索引中可以包含多个类型(表),每个类型下又包含多个文档(行),每个文档中又包含多个字段(列)
物理设计
ES在后台把索引划分为多个分片,每个分片可以在集群中的不同服务之间迁移
默认单个机器就是一个集群
{
"name": "DESKTOP-0QNFHPB",
"cluster_name": "elasticsearch",
"cluster_uuid": "WEED5DuuT1Oycb19zH_hrw",
"version": {
"number": "7.6.1",
"build_flavor": "default",
"build_type": "zip",
"build_hash": "aa751e09be0a5072e8570670309b1f12348f023b",
"build_date": "2020-02-29T00:15:25.529771Z",
"build_snapshot": false,
"lucene_version": "8.4.0",
"minimum_wire_compatibility_version": "6.8.0",
"minimum_index_compatibility_version": "6.0.0-beta1"
},
"tagline": "You Know, for Search"
}
逻辑设计
一个索引类型中,包含多个文档,比如文档1,文档2。当我们索引一篇文档的时候,可以通过这样的顺序找到它:索引>类型>文档ID,通过这个组合我们就能索引到某个具体的文档,注意:ID不必是整数,实际上就是一个字符串
文档
就是我们的一条条数据
user
1 zhangsan 18
2 lisi 19
之前所说的ES是面向文档的,那么就意味着索引和搜索数据的最小单位是文档,ES中,文档有几个重要属性
尽管我们可以随意的新增或者忽略某个字段,但是每个字段的类型十分重要,比如一个年龄字段类型,可以是字符串,也可以是整形,因为ES会保存字段和类型之间的映射和其他的设置,这种映射具体到每个映射的每种类型,这也是为什么在ES中,类型有时候也称为映射类型
类型
类型是文档的逻辑容器,就像关系型数据库一样,表格是行的容器,类型对于字段称为映射,比如name映射围殴字符串类型。我们所文档是无模式的,它们不需要拥有映射中所定义的所有字段,比如新增一个字段,那么ES怎么做的呢?ES会自动的讲新字段加入映射,但是这个总段不确定它是什么类型,ES就开始猜,如果这个值是18,那么ES会认为他是整型。但是ES也有可能猜不对,素以最安全的方式就是提前定义好所需的映射,这点即和关系型数据库殊途同归了,先定义好字段,然后再使用。
索引
就是数据库
索引是映射类型的容器,ES中的索引是一个非常强大的文档集合,索引存储了映射类型的字段和其他设置。然后他们被存储到了各个分片上
物理设置:节点和分片 如何工作
一个集群至少有一个节点,而一个节点就是一个ES进程,节点可以有多个索引默认的,如果你创建索引,那么索引将会有5个分片(primary shard 又是主分片)构成的,每一个主分片都有一个副本(replica shard 又称复制分片)
每一个底层的分片都是一个Lucene索引,一个包含倒排索引的文件目录,倒排索引的结构使得ES再不扫描全表的情况下,就能告诉你文档包含哪些特定的关键字,
倒排索引
ES使用的是一种倒排索引的结构,采用Lucene到排序最为底层,这种结构适用于快速的全文搜索,一个索引由文档中所有不重复的列表构成,对于每一个词,都有一个包含他的文件列表,
什么是IK分词器?
分词:就是把一段中文或者别的划分成一个个的关键字,我们在搜索的时候会把我们的信息进行分词,会把数据库中或者索引库中的数据进行分词,然后进行一个匹配操作,默认的中文分词器是讲每个字看作一个词,比如:"你好,世界" => "你","好","世","界",",",这显然是不符合要求的,所以需要按章IK中文分词器来解决这个问题
IK提供了两个分词算法:ik_smart和ik_max_word,其中ik_smart为最少切分,ik_max_word为最细粒度划分!
安装
下载完毕之后,放入到我们的ES插件即可
重启即可,可以看到ik分词器已经被加载
可以通过 elasticsearch-plugin list
来查看安装的插件
使用kibana操作
查看不同的分词器效果
GET _analyze
{
"analyzer": "ik_smart",
"text": "爱我中华,56个民族,中国共产党"
}
结果
{
"tokens" : [
{
"token" : "爱我",
"start_offset" : 0,
"end_offset" : 2,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "中华",
"start_offset" : 2,
"end_offset" : 4,
"type" : "CN_WORD",
"position" : 1
},
{
"token" : "56个",
"start_offset" : 5,
"end_offset" : 8,
"type" : "TYPE_CQUAN",
"position" : 2
},
{
"token" : "民族",
"start_offset" : 8,
"end_offset" : 10,
"type" : "CN_WORD",
"position" : 3
},
{
"token" : "中国共产党",
"start_offset" : 11,
"end_offset" : 16,
"type" : "CN_WORD",
"position" : 4
}
]
}
GET _analyze
{
"analyzer": "ik_max_word",
"text": "爱我中华,56个民族,中国共产党"
}
结果
{
"tokens" : [
{
"token" : "爱我",
"start_offset" : 0,
"end_offset" : 2,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "中华",
"start_offset" : 2,
"end_offset" : 4,
"type" : "CN_WORD",
"position" : 1
},
{
"token" : "56",
"start_offset" : 5,
"end_offset" : 7,
"type" : "ARABIC",
"position" : 2
},
{
"token" : "个",
"start_offset" : 7,
"end_offset" : 8,
"type" : "COUNT",
"position" : 3
},
{
"token" : "民族",
"start_offset" : 8,
"end_offset" : 10,
"type" : "CN_WORD",
"position" : 4
},
{
"token" : "中国共产党",
"start_offset" : 11,
"end_offset" : 16,
"type" : "CN_WORD",
"position" : 5
},
{
"token" : "中国",
"start_offset" : 11,
"end_offset" : 13,
"type" : "CN_WORD",
"position" : 6
},
{
"token" : "国共",
"start_offset" : 12,
"end_offset" : 14,
"type" : "CN_WORD",
"position" : 7
},
{
"token" : "共产党",
"start_offset" : 13,
"end_offset" : 16,
"type" : "CN_WORD",
"position" : 8
},
{
"token" : "共产",
"start_offset" : 13,
"end_offset" : 15,
"type" : "CN_WORD",
"position" : 9
},
{
"token" : "党",
"start_offset" : 15,
"end_offset" : 16,
"type" : "CN_CHAR",
"position" : 10
}
]
}
输出 超级喜欢狂神说Java
发现问题:自己需要的词被拆开了
这个时候就需要自己添加分词器
进入目录:F:\EnvironmentHomes\elasticsearch-7.6.1\plugins\ik\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>
然后重启ES
以后的话,我们需要自己配置 分词就在自己定义的dic文件执行配置即可
一种软件架构风格,而不是标准,知识提供了一组设计原则和约束条件。它主要用户客户端和服务器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。
基本Rest命令说明
method | url地址 | 描述 |
---|---|---|
PUT | localhost:9200/索引名称/类型名称/文档id | 创建文档(指定文档id) |
POST | localhost:9200/索引名称/类型名称 | 创建文档(随机文档id) |
POST | localhost:9200/索引名称/类型名称/文档id/_update | 修改文档 |
DELETE | localhost:9200/索引名称/类型名称/文档id | 删除文档 |
GET | localhost:9200/索引名称/类型名称/文档id | 查询文档通过文档id |
POST | localhost:9200/索引名称/类型名称/_search | 查询所有数据 |
基础测试
创建一个索引
# PUT /索引名/~类型名~/文档id
# {请求体}
PUT /test1/type1/1
{
"name":"ic学Java,狂神说",
"age":3
}
完成了自动增加了索引 数据也成功的添加了,可以当作数据库学习
数据类型
指定字段的类型
创建规则:
PUT /test2
{
"mappings": {
"properties": {
"name":{
"type": "text"
},
"age":{
"type": "integer"
},
"birth":{
"type": "date"
}
}
}
}
获得规则:
GET test2
查看默认的信息
如果自己的文档字段没有指定 ,那么ES就会给我们默认配置字段类
拓展:通过命令ElasticSearch索引情况,通过 get _cat 可以获得ES的很多信息
修改 提交使用PUT即可,然后覆盖,最新办法 曾经的办法
修改之后版本号会增加,然后状态时 update
现在的方法
删除
根据你的请求命令来判断是删除索引还是删除文档记录
使用Rest风格是ES推荐使用的
添加数据
PUT /ic/user/1
{
"name":"狂神说",
"age":23,
"desc":"你我山前没相见,山后别相逢",
"tags":["宅男","许嵩","阳光色"]
}
PUT /ic/user/2
{
"name":"璨詞",
"age":23,
"desc":"你我山前没相见,删后别相逢",
"tags":["许嵩","阳光色"]
}
PUT /ic/user/3
{
"name":"张三",
"age":29,
"desc":"座扣想张三",
"tags":["渣男","阳光色"]
}
得到数据 使用GET
GET /ic/user/1
GET ic/user/_search?q=name:张三
修改/更新数据 使用PUT
# 这样会导致数据丢失,不传值就是用 空 覆盖
PUT /ic/user/3
{
"name":"张三2号",
"age":29,
"desc":"座扣想张三",
"tags":["渣男","阳光色"]
}
更新 POST _update 推荐使用这种更新方式
# 这样也会导致数据丢失
POST /ic/user/3
{
"name":"张三3"
}
# 这里如果不加 _update 就会消除其他的数据
POST /ic/user/3
{
"doc":{
"name":"张三4"
}
}
# 这是推荐使用的方法
POST /ic/user/3/_update
{
"doc":{
"name":"张三4"
}
}
输出结果,不想要那么多
我们之后使用Java操作ES的时候,所有的方法和对象就是这里的key
排序
分页查询
数据下标是从0开始的
/search/{current}/{pagesize}
布尔值查询
must 命令 所有的条件都要符合
should 符合一个就可以
过滤器
匹配多个条件
精确查询
term 查询时直接通过倒排索引指定的词条进程精确查找的
关于分词,
直接查询精确的值
match会使用分词器解析(先分析文档,在通过分析的文档进行查询)
两个类型 text会被分词器解析 但是 keyword不会被分词器解析
多个精确查询
高亮查询
找文档
找到原生的依赖
<!-- 加入ElasticSearch搜索引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
找到对象
分析API的方法
配置基本的项目
问题:一定要保证导入的依赖和我们的ES版本是一致的
索引的操作
@SpringBootTest
class IcEsApiApplicationTests {
@Autowired
@Qualifier("restHighLevelClient")
private RestHighLevelClient client;
@Test
void createIndex() throws IOException {
// 测试索引的创建
// 创建索引请求
CreateIndexRequest request = new CreateIndexRequest("ic_index");
// 执行创建请求
CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
System.out.println(response.index());
}
@Test
public void getIndex() throws Exception{
// 测试获取索引
GetIndexRequest request = new GetIndexRequest("ic_index");
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
System.out.println(exists);
}
@Test
public void deleteIndex() throws IOException{
// 测试获取索引
DeleteIndexRequest request = new DeleteIndexRequest("ic_index");
AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);
System.out.println(delete.isAcknowledged());
}
}
package cn.icanci;
import cn.icanci.pojo.User;
import com.alibaba.fastjson.JSON;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.elasticsearch.action.search.SearchRequest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
@SpringBootTest
class IcEsApiApplicationTests {
@Autowired
@Qualifier("restHighLevelClient")
private RestHighLevelClient client;
@Test
void createIndex() throws IOException {
// 测试索引的创建
// 创建索引请求
CreateIndexRequest request = new CreateIndexRequest("ic_index");
// 执行创建请求
CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
System.out.println(response.index());
}
@Test
public void getIndex() throws Exception {
// 测试获取索引
GetIndexRequest request = new GetIndexRequest("ic_index");
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
System.out.println(exists);
}
@Test
public void deleteIndex() throws IOException {
// 测试获取索引
DeleteIndexRequest request = new DeleteIndexRequest("ic_index");
AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);
System.out.println(delete.isAcknowledged());
}
@Test
public void testAddDocument() throws Exception {
// 测试添加文档
// 创建对象
User user = new User("ic", 2);
// 创建请求
IndexRequest request = new IndexRequest("ic_index");
// 设置规则 put /ic_index/_doc/1
request.id("1");
request.timeout(TimeValue.timeValueSeconds(1));
// request.timeout("1s");
// 将数据放入请求
request.source(JSON.toJSONString(user), XContentType.JSON);
// 客户端发送请求,获取相应的结果
IndexResponse index = client.index(request, RequestOptions.DEFAULT);
System.out.println(index.toString());
System.out.println(index.status());
}
@Test
public void testIsExists() throws Exception {
// 判断文档是否存在 get /index/doc/1
GetRequest ic_index = new GetRequest("ic_index", "1");
// 不获取 返回的 _source 的上下文了
ic_index.fetchSourceContext(new FetchSourceContext(false));
ic_index.storedFields("_none_");
boolean exists = client.exists(ic_index, RequestOptions.DEFAULT);
System.out.println(exists);
}
@Test
public void testGetInfo() throws Exception {
// 判断文档是否存在 get /index/doc/1
GetRequest ic_index = new GetRequest("ic_index", "1");
GetResponse documentFields = client.get(ic_index, RequestOptions.DEFAULT);
System.out.println(documentFields.getSourceAsString());
System.out.println(documentFields);
}
@Test
public void testUpdate() throws Exception {
// 判断文档是否存在 get /index/doc/1
UpdateRequest updateRequest = new UpdateRequest("ic_index", "1");
updateRequest.timeout("1s");
User user = new User("ic", 18);
updateRequest.doc(JSON.toJSONString(user), XContentType.JSON);
UpdateResponse update = client.update(updateRequest, RequestOptions.DEFAULT);
System.out.println(update);
}
@Test
public void testDelete() throws Exception {
// 删除数据
DeleteRequest deleteRequest = new DeleteRequest("ic_index", "1");
deleteRequest.timeout("1s");
DeleteResponse delete = client.delete(deleteRequest, RequestOptions.DEFAULT);
System.out.println(delete.status());
}
@Test
public void testBulkRequest() throws Exception {
// 批量增加数据
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.timeout("10s");
ArrayList<User> users = new ArrayList<>();
for (int i = 0; i < 10; i++) {
users.add(new User("ic" + i, i));
}
// 批处理请求
for (int i = 0; i < users.size(); i++) {
bulkRequest.add(new IndexRequest("ic_index")
.id("1" + i)
.source(JSON.toJSONString(users.get(i)), XContentType.JSON));
}
BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
System.out.println(bulk.hasFailures());
}
@Test
public void testSearch() throws Exception {
// 查询
// 搜索请求,
// 条件构造
// termQueryBuilder
// xxxQueryBuilder 类似于次
SearchRequest searchRequest = new SearchRequest("ic_index");
// 构建搜索的条件
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// QueryBuilders.termQuery 精确匹配
// QueryBuilders.matchAllQuery() 匹配所有
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "ic0");
sourceBuilder.query(termQueryBuilder);
sourceBuilder.from();
sourceBuilder.size();
sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
searchRequest.source(sourceBuilder);
SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
for (SearchHit searchHits : search.getHits()) {
System.out.println(searchHits.getSourceAsMap());
}
}
}
爬取数据?数据库获取,消息队列中获取,都可以称为数据源
爬取数据:(获取请求返回的页面信息。筛选初我们自己想要的数据就可以了)
jsoup包 tika 爬取电影和音乐
导入依赖
<!-- 解析网页 -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.10.2</version>
</dependency>
@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Content {
private String title;
private String img;
private String price;
}
@Configuration
public class ElasticSearchConfig {
@Bean
public RestHighLevelClient restHighLevelClient() {
RestHighLevelClient restHighLevelClient = new RestHighLevelClient(
RestClient.builder(
new HttpHost("127.0.0.1", 9200, "http")
)
);
return restHighLevelClient;
}
}
@Component
public class HtmlParseUtil {
public List<Content> parseJD(String keyword) throws IOException {
// 获得请求 https://search.jd.com/Search?keyword=java
// 前提,需要联网 ajax 不能获取
String url = "https://search.jd.com/Search?keyword=" + keyword;
// 解析网页 就是js页面对象
Document parse = Jsoup.parse(new URL(url), 30000);
// 所有在 JS 中能使用的方法都可以获取
Element element = parse.getElementById("J_goodsList");
// 获取所有的元素
Elements li = element.getElementsByTag("li");
// 获取元素的内容
ArrayList<Content> goodLists = new ArrayList<Content>();
for (Element ele : li) {
String img = ele.getElementsByTag("img").eq(0).attr("src");
String price = ele.getElementsByClass("p-price").eq(0).text();
String title = ele.getElementsByClass("p-name").eq(0).text();
goodLists.add(new Content(title, img, price));
}
return goodLists;
}
}
package cn.icanci.service;
/**
* @Author: icanci
* @ProjectName: ic-es-jd
* @PackageName: cn.icanci.service
* @Date: Created in 2020/7/21 7:51
* @ClassAction: 业务编写
*/
@Service
public class ContentService {
@Autowired
@Qualifier("restHighLevelClient")
private RestHighLevelClient client;
@Autowired
private HtmlParseUtil htmlParseUtil;
/**
* 解析数据放到 ES 索引中
*
* @param keyword
* @return
* @throws Exception
*/
public boolean parseContent(String keyword) throws Exception {
List<Content> contents = htmlParseUtil.parseJD(keyword);
// 把查询的数据放在 es 中
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.timeout("2m");
if (contents == null || contents.size() == 0) {
return false;
}
for (int i = 0; i < contents.size(); i++) {
bulkRequest.add(new IndexRequest("jd_goods")
.source(JSON
.toJSONString(contents.get(i)), XContentType.JSON));
}
BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
return !bulk.hasFailures();
}
/**
* 获取这些数据实现搜索功能
*
* @param keyword
* @param pageNo
* @param pageSize
* @return
*/
public List<Map<String, Object>> searchPage(String keyword, int pageNo, int pageSize) throws Exception {
if (pageNo < 1) {
pageNo = 1;
}
if (pageSize < 1) {
pageSize = 20;
}
// 条件搜索
SearchRequest searchRequest = new SearchRequest("jd_goods");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 分页实现
sourceBuilder.from(pageNo);
sourceBuilder.size(pageSize);
// 精准匹配
TermQueryBuilder title = QueryBuilders.termQuery("title", keyword);
sourceBuilder.query(title);
sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
// 执行搜索
searchRequest.source(sourceBuilder);
SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
// 解析结果
ArrayList<Map<String, Object>> maps = new ArrayList<>();
for (SearchHit hit : search.getHits().getHits()) {
maps.add(hit.getSourceAsMap());
}
return maps;
}
/**
* 获取这些数据实现搜索功能
*
* @param keyword
* @param pageNo
* @param pageSize
* @return
*/
public List<Map<String, Object>> searchPageHigh(String keyword, int pageNo, int pageSize) throws Exception {
if (pageNo < 1) {
pageNo = 1;
}
if (pageSize < 1) {
pageSize = 20;
}
// 条件搜索
SearchRequest searchRequest = new SearchRequest("jd_goods");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 分页实现
sourceBuilder.from(pageNo);
sourceBuilder.size(pageSize);
// 精准匹配
TermQueryBuilder title = QueryBuilders.termQuery("title", keyword);
sourceBuilder.query(title);
sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
// 高亮
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("title");
// 多个高亮一个
highlightBuilder.requireFieldMatch(false);
highlightBuilder.preTags("<span style='color:red'>");
highlightBuilder.postTags("</span>");
sourceBuilder.highlighter(highlightBuilder);
// 执行搜索
searchRequest.source(sourceBuilder);
SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
// 解析结果
ArrayList<Map<String, Object>> maps = new ArrayList<>();
for (SearchHit hit : search.getHits().getHits()) {
// 解析高亮的字段
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
HighlightField title1 = highlightFields.get("title");
// 原来的结果
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
//
if (title1 != null) {
Text[] fragments = title1.fragments();
String new_title = "";
for (Text fragment : fragments) {
new_title += fragment;
}
// 替换原来的内容
sourceAsMap.put("title", new_title);
}
maps.add(sourceAsMap);
}
return maps;
}
}
package cn.icanci.controller;
import java.util.List;
import java.util.Map;
/**
* @Author: icanci
* @ProjectName: ic-es-jd
* @PackageName: cn.icanci.controller
* @Date: Created in 2020/7/21 7:51
* @ClassAction: 请求编写
*/
@RestController
public class ContentController {
@Autowired
private ContentService contentService;
@GetMapping("/parse/{keyword}")
public boolean parse(@PathVariable("keyword") String keyword) throws Exception {
System.out.println("keyword=>" + keyword);
return contentService.parseContent(keyword);
}
@GetMapping("/search/{keyword}/{pageNo}/{pageSize}")
public List<Map<String, Object>> search(
@PathVariable("keyword") String keyword,
@PathVariable("pageNo") int pageNo,
@PathVariable("pageSize") int pageSize) throws Exception {
// 获取数据实现搜索功能
return contentService.searchPage(keyword, pageNo, pageSize);
}
@GetMapping("/searchHighLight/{keyword}/{pageNo}/{pageSize}")
public List<Map<String, Object>> searchHighLight(
@PathVariable("keyword") String keyword,
@PathVariable("pageNo") int pageNo,
@PathVariable("pageSize") int pageSize) throws Exception {
// 获取数据实现搜索功能 并高亮
return contentService.searchPageHigh(keyword, pageNo, pageSize);
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8"/>
<title>IC-ES仿京东实战</title>
<link rel="stylesheet" th:href="@{/css/style.css}"/>
</head>
<body class="pg">
<div class="page" id="app">
<div id="mallPage" class=" mallist tmall- page-not-market ">
<!-- 头部搜索 -->
<div id="header" class=" header-list-app">
<div class="headerLayout">
<div class="headerCon ">
<!-- Logo-->
<h1 id="mallLogo">
<img th:src="@{/images/jdlogo.png}" alt="">
</h1>
<div class="header-extra">
<!--搜索-->
<div id="mallSearch" class="mall-search">
<form name="searchTop" class="mallSearch-form clearfix">
<fieldset>
<legend>天猫搜索</legend>
<div class="mallSearch-input clearfix">
<div class="s-combobox" id="s-combobox-685">
<div class="s-combobox-input-wrap">
<input v-model="keyword" type="text" autocomplete="off" value="dd" id="mq"
class="s-combobox-input" aria-haspopup="true">
</div>
</div>
<button type="submit" @click.prevent="searchKey" id="searchbtn">搜索</button>
</div>
</fieldset>
</form>
<ul class="relKeyTop">
<li><a>Java</a></li>
<li><a>前端</a></li>
<li><a>Linux</a></li>
<li><a>大数据</a></li>
<li><a>理财</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- 商品详情页面 -->
<div id="content">
<div class="main">
<!-- 品牌分类 -->
<form class="navAttrsForm">
<div class="attrs j_NavAttrs" style="display:block">
<div class="brandAttr j_nav_brand">
<div class="j_Brand attr">
<div class="attrKey">
品牌
</div>
<div class="attrValues">
<ul class="av-collapse row-2">
<li><a href="#"> 狂神说 </a></li>
<li><a href="#"> Java </a></li>
</ul>
</div>
</div>
</div>
</div>
</form>
<!-- 排序规则 -->
<div class="filter clearfix">
<a class="fSort fSort-cur">综合<i class="f-ico-arrow-d"></i></a>
<a class="fSort">人气<i class="f-ico-arrow-d"></i></a>
<a class="fSort">新品<i class="f-ico-arrow-d"></i></a>
<a class="fSort">销量<i class="f-ico-arrow-d"></i></a>
<a class="fSort">价格<i class="f-ico-triangle-mt"></i><i class="f-ico-triangle-mb"></i></a>
</div>
<!-- 商品详情 -->
<div class="view grid-nosku">
<div class="product" v-for="result in results">
<div class="product-iWrap">
<!--商品封面-->
<div class="productImg-wrap">
<a class="productImg">
<img :src="result.img">
</a>
</div>
<!--价格-->
<p class="productPrice">
<em>{{result.price}}</em>
</p>
<!--标题-->
<p class="productTitle">
<a v-html="result.title"></a>
</p>
<!-- 店铺名 -->
<div class="productShop">
<span>店铺: 狂神说Java </span>
</div>
<!-- 成交信息 -->
<p class="productStatus">
<span>月成交<em>999笔</em></span>
<span>评价 <a>3</a></span>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 前端使用Vue 实现前后端分离 -->
<script th:src="@{/js/jquery.min.js}"></script>
<script th:src="@{/js/vue.min.js}"></script>
<script th:src="@{/js/axios.min.js}"></script>
<script>
new Vue({
el: '#app',
data: {
keyword: '', // 搜索的关键字
results: [] // 搜索的结果
},
methods: {
searchKey(){
var keyword = this.keyword;
// 对接后端的接口
axios.get('searchHighLight/'+keyword+"/1/30").then(response=>{
console.log(response)
this.results = response.data; // 绑定数据
})
}
}
})
</script>
</body>
</html>
已经包含在上述内容
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。