一文包学会ElasticSearch的大部分应用场合

news2024/11/19 3:38:59

ElasticSearch

官网下载地址:Download Elasticsearch | Elastic

历史版本下载地址1:Index of elasticsearch-local/7.6.1

历史版本下载地址2:Past Releases of Elastic Stack Software | Elastic

ElasticSearch的安装(windows)

安装前所需环境,jdk1.8以上,最好也要有node环境。

  1. 安装解压即可

  2. 启动进入bin目录,点击

image20221212100648303.png

启动日志

image20221212100810247.png

访问端口:http://localhost:9200/

image20221212100914770.png

安装可视化界面

下载地址:GitHub - mobz/elasticsearch-head: A web front end for an elastic search cluster

#下载依赖
cnpm install
#启动
cnpm run start
#默认启动本地的9100端口

连接ElasticSearch

这里会出现跨域问题

修改ElasticSearch配置文件(在elasticsearch解压目录config下elasticsearch.yml中添加)

# 开启跨域
http.cors.enabled: true
# 所有人访问
http.cors.allow-origin: "*"

image20221212103503756.png

安装kibana

官网地址:下载 Elastic 产品 | Elastic

历史版本地址:Index of kibana-local/7.6.1

下载的版本要和ElasticSearch版本一致

解压,进入斌、目录启动。

image20221212110136911.png

汉化

修改config文件下的kibana.yml配置文件。

i18n.locale: "zh-CN"

image20221212111016942.png

image20221212111917082.png

docker安装(单点)

链接: 百度网盘 请输入提取码 提取码: cf82

1.将三个tar放置/tmp下

2.在该目录下执行

 docker load -i es.tar  #上到到容器
docker load -i kibana.tar 

3.执行启动

docker run -d \
	--name es \
    -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
    -e "discovery.type=single-node" \
    -v es-data:/usr/share/elasticsearch/data \
    -v es-plugins:/usr/share/elasticsearch/plugins \
    --privileged \
    --network es-net \
    -p 9200:9200 \
    -p 9300:9300 \
elasticsearch:7.12.1

命令解释:

  • -e "cluster.name=es-docker-cluster":设置集群名称

  • -e "http.host=0.0.0.0":监听的地址,可以外网访问

  • -e "ES_JAVA_OPTS=-Xms512m -Xmx512m":内存大小

  • -e "discovery.type=single-node":非集群模式

  • -v es-data:/usr/share/elasticsearch/data:挂载逻辑卷,绑定es的数据目录

  • -v es-logs:/usr/share/elasticsearch/logs:挂载逻辑卷,绑定es的日志目录

  • -v es-plugins:/usr/share/elasticsearch/plugins:挂载逻辑卷,绑定es的插件目录

  • --privileged:授予逻辑卷访问权

  • --network es-net :加入一个名为es-net的网络中

  • -p 9200:9200:端口映射配置

在浏览器中输入:http://IP地址:9200 即可看到elasticsearch的响应结果

 4.接下来安装kibana

运行docker命令,部署kibana

docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=es-net \
-p 5601:5601  \
kibana:7.12.1
  • --network es-net :加入一个名为es-net的网络中,与elasticsearch在同一个网络中

  • -e ELASTICSEARCH_HOSTS=http://es:9200":设置elasticsearch的地址,因为kibana已经与elasticsearch在一个网络,因此可以用容器名直接访问elasticsearch

  • -p 5601:5601:端口映射配置

kibana启动一般比较慢,需要多等待一会,可以通过命令:

docker logs -f kibana

查看运行日志,当查看到下面的日志,说明成功:

此时,在浏览器输入地址访问:http://192.168.150.101:5601,即可看到结果

docker安装分词器(在线安装)

# 进入容器内部
docker exec -it elasticsearch /bin/bash

# 在线下载并安装
./bin/elasticsearch-plugin  install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip

#退出
exit
#重启容器
docker restart elasticsearch

docker安装ik分词器(离线安装)

在百度网盘中提出ik文件夹或者自己解压elasticsearch-analysis-ik-7.12.1.zip重命名为ik,如下操作:

安装插件需要知道elasticsearch的plugins目录位置,而我们用了数据卷挂载,因此需要查看elasticsearch的数据卷目录,通过下面命令查看:

docker volume inspect es-plugins

显示结果:

[
    {
        "CreatedAt": "2022-05-06T10:06:34+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/es-plugins/_data",
        "Name": "es-plugins",
        "Options": null,
        "Scope": "local"
    }
]

说明plugins目录被挂载到了:/var/lib/docker/volumes/es-plugins/_data这个目录中。

然后进入该文件夹将ik文件夹放置该目录即可:

依次执行以下指令:

 # 重启容器
 docker restart es
 
 # 查看es日志
docker logs -f es

踩坑:

我的是虚拟机,暂停之后,docker无法挂载导致。

#重启docker
systemctl restart docker
#重启容器
systemctl restart es
systemctl restart kibana

测试:

IK分词器

什么是ik分词器?

IK分词器是ES的一个插件,主要用于把一段中文或者英文的划分成一个个的关键字,我们在搜索时候会把自己的信息进行分词,会把数据库中或者索引库中的数据进行分词,然后进行一个匹配操作,默认的中文分词器是将每个字看成一个词,比如"我爱技术"会被分为"我","爱","技","术",这显然不符合要求,所以我们需要安装中文分词器IK来解决这个问题;

IK提供了两个分词算法:ik_smart和ik_max_word

ik_smart为最少切分,添加了歧义识别功能,推荐;

ik_max_word为最细切分,能切的都会被切掉;

示例:对“买一台笔记本” 进行分词 K提供了两个分词算法: ik_smartik_max_word ,其中ik_smart最少切分, ik_max_word最细粒度划分!

ik_smart分词结果:

img

ik_max_word分词结果:

{
  "tokens" : [
    {
      "token" : "买一",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "一台",
      "start_offset" : 1,
      "end_offset" : 3,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "一",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "TYPE_CNUM",
      "position" : 2
    },
    {
      "token" : "台笔",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 3
    },
    {
      "token" : "台",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "COUNT",
      "position" : 4
    },
    {
      "token" : "笔记本",
      "start_offset" : 3,
      "end_offset" : 6,
      "type" : "CN_WORD",
      "position" : 5
    },
    {
      "token" : "笔记",
      "start_offset" : 3,
      "end_offset" : 5,
      "type" : "CN_WORD",
      "position" : 6
    },
    {
      "token" : "本",
      "start_offset" : 5,
      "end_offset" : 6,
      "type" : "CN_CHAR",
      "position" : 7
    }
  ]
}

安装

下载地址:https://github.com/medcl/elasticsearch-analysis-ik/releases/tag/v7.6.1

下载完成后解压到,在elasticSearch的plugins目录下创建ik文件目录并解压。

image20221212143235660.png

配置完成使用kibanna进行测试:

image20221212145635558.png

仔细观察可发现es会将一个一个的词去分开,那么他是根据什么去分词的呢?

我们进入下载的ik分词器插件中可见有很多的.dic文件,其实这些文件就是为了分词所诞生的。

image20221212145938908.png

自定义分词

例子:此时我们想让上图中的久久不见为一组

  1. 在config目录下创建my.dic

    image20221212150753059.png

  2. 然后在IKAnalyzer.cfg.xml

image20221212151044126.png

3.重启测试

image20221212151236201.png

image20221212151448605.png

Rest风格说明

一种软件架构风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁更有层次更易于实现缓存等机制。

基本Rest命令说明:

methodurl地址描述
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/索引名称/类型名称/文档id/_search查询所有数据

测试

PUT /test1/type1/1
{
  "name" : "测试",
  "age" : 18
}

image20221212152901520.png

image20221212152931182.png

字段数据类型(Mapping)

  • 字符串类型

    • text、

      keyword

      • text:支持分词,全文检索,支持模糊、精确查询,不支持聚合,排序操作;text类型的最大支持的字符长度无限制,适合大字段存储;

      • keyword:不进行分词,直接索引、支持模糊、支持精确匹配,支持聚合、排序操作。keyword类型的最大支持的长度为——32766个UTF-8类型的字符,可以通过设置ignore_above指定自持字符长度,超过给定长度后的数据将不被索引,无法通过term精确匹配检索返回结果。

  • 数值型

    • long、Integer、short、byte、double、float、half floatscaled float

  • 日期类型

    • date

  • te布尔类型

    • boolean

  • 二进制类型

    • binary

  • 等等…

指定字段的类型(put)

PUT /test2
{
  "mappings": {
    "properties": {
      "name":{ #类似数据库字段名
        "type": "text" #类似数据库字段类型
      },
      "age":{
        "type": "long"
      }
    }
  }
  }

image20221212153825182.png

crud例子

# 创建索引库
PUT /estest
{
  "mappings": {
    "properties": {
      "info": {
        "type": "text",
        "analyzer": "ik_max_word" #使用text类型需要指定ik分词器类型
      },
      "email": {
        "type": "keyword",
        "index": false #是够开启分词,默认开启
      },
      "name": {
        "type": "object",
        "properties": {
          "firstName": {
            "type": "keyword",
            "index": false
          },
          "lastName":{
            "type": "keyword",
            "index": false
          }
        }
      }
    }
  }
}
# 查询
get /estest
#修改结构 在原结构的基础上添加age属性并指定类型
PUT /estest/_mapping
{
  "properties":{
    "age":{
      "type":"integer"
    }
  }
}
#删除索引
DELETE /estest

关于索引的基本操作

1.添加数据

PUT  test/_doc/2
{
  "name":"王五",
  "age":35,
  "desc":"啦啦啦啦啦测试啦",
  "tag":["标签一","标签二","标签三"]
}

//在原来的结构上添加字段
POST /hotel/_update/2048671293
{
  "doc": {
    "isAD":true
  }
}

image20221212162321518.png

2.修改数据

#指定所有的字段做修改操作
POST  test/_doc/2/_update
{
"name":"修改的名字"
}

3.模糊查询

GET test/_doc/_search?q=name:张

image20221212164547096.png

GET test/_doc/_search
{
  "query":{
    "match":{
      "name":"张"
    }
  }
}

image20221212171031576.png

查询匹配

  • match:匹配(会使用分词器解析(先分析文档,然后进行查询))

  • _source:过滤字段

  • sort:排序

  • formsize 分页

4._source:过滤字段

GET test/_doc/_search
{
  "query":{
    "match":{
      "name":"张"
    }
  },
  "_source":["name","tag"] #只需要name和tag属性
}

5.sort排序

GET test/_doc/_search
{
  "query":{
    "match":{
      "name":"张"
    }
  },
  "sort":[
        {
          "age":{
            "order":"desc" #asc desc 
          }
        }
      ]
}

6.formsize 分页

GET test/_doc/_search
{
  "query":{
    "match":{
      "name":"张"
    }
  },
  "sort":[
    {
      "age":{
        "order":"desc"
            }
    }
    ],
    "from":1, #from相当于 limit(pageNnm) 第几页
    "size":1  #size 相当于 pageSize 每页显示多少条数据
}

7.布尔值查询(must)

GET test/_doc/_search
{
  "query":{
    "bool":{
      "must":[ #must表示必须符合的意思
            {
              "match":{
                "name":"张"
              }
            },
            {
              "match":{
                "age":23
              }
            }
        ]
    }
  }
}
#上面相当于mysql的 where name='张' and  age=23

8.相当于sql的or语句(should)

GET  /test/_doc/_search
{
  "query":{
      "bool": {
        "should": [
          {
           "match":{
             "name":"张三"
           }
          },
          {
            "match":{
              "age":88
            }
          }
        ]
      }    
  }
}
#相当于mysql的 where name="张三" or age=88 

9.不包含(must_not)

GET /test/_doc/_search
{
  "query":{
    "bool":{
      "must_not":{ #不包含
        "match":{
          "age":88 #排除88岁的
        }
      } 
    }
  }
}

10.过滤器(lt)

GET  /test/_doc/_search
{
  "query":{
      "bool": {
        "should": [
          {
           "match":{
             "name":"张三"
           }
          },
          {
            "match":{
              "age":88
            }
          }
        ],
        "filter":{
          "range":{
            "age":{
              "lt":88, # 小于88
              "gt":23  # 大于23 
            }
          }
        }
      }    
  }
}
  • gt 大于

  • gte 大于等于

  • lt 小于

  • lte 小于等于

11.匹配多个条件查询

GET /test/_doc/_search
{
 "query":{
   "match":{
     "tag":"小 大"
   }
 }  
}

image20221214135428261.png

12.精确查询

  1. term 查找是通过倒排索引去查询的查询快

  2. match 会使用分词器解析,通过分词查询

注意类型:其中keyword类型是不会被分词器解析拆分的,而text会被拆分。

GET /test3/_doc/_search
{
  "query":{
    "term": {#使用term必须精确,因为desc这个字段的类型的keyword
      "desc": "无语了..."
    }
  }
}

GET /test3/_doc/_search
{
  "query":{
      "match":{ 
         "name":"老"
      }
  }
}

精确查询多个值

image20221214144146078.png

例子

# 创建索引库
PUT /estest
{
  "mappings": {
    "properties": {
      "info": {
        "type": "text",
        "analyzer": "ik_max_word"
      },
      "email": {
        "type": "keyword",
        "index": false
      },
      "name": {
        "type": "object",
        "properties": {
          "firstName": {
            "type": "keyword",
            "index": false
          },
          "lastName":{
            "type": "keyword",
            "index": false
          }
        }
      }
    }
  }
}
# 查询
#get /estest
#修改结构 在原结构的基础上添加age属性并指定类型
PUT /estest/_mapping
{
  "properties":{
    "age":{
      "type":"integer"
    }
  }
}
#删除索引
DELETE /estest


#添加文档1
PUT /estest/_doc/1
{
  "age":10,
  "email":"24112869@qq.com",
   "info":"这是一段分词的内容!",
   "name":{
     "firstName":"张",
     "lastName":"三"
   }
}
#添加文档2
PUT /estest/_doc/2
{
  "age":50,
  "email":"78945669@qq.com",
   "info":"这是一段分词的内容2!",
   "name":{
     "firstName":"张",
     "lastName":"四"
   }
}

#查询文档
#get  /estest/_doc/2

#模糊查询方式1
GET /estest/_doc/_search?q=info:这是

#模糊查询方式2
POST /estest/_search
{
  "query": {
    "match": {
      "info": "这是"
    }
  }
}
#过滤字段
POST /estest/_search
{
  "query": {
    "match": {
      "info": "这是"
    }
  },
  "_source": ["name","info","age"],
  "sort": [
    {
      "age": {
        "order": "desc"
      }
    }
  ]
}
# 代码高亮
GET /estest/_search
{
  "query":{
    "match":{
      "info":"这是"
    }
  },
  "highlight":{
    "pre_tags":"<i class='key' style='color:red'>",
    "post_tags":"</i>",
    "fields":{
      "info":{}
    }
  }
}

代码高亮

GET /test3/_doc/_search
{
  "query":{
    "match":{
      "name":"李"
    }
  },
  "highlight":{
    "fields":{
      "name":{}
    }
  } 
}

image20221214145003525.png

返回前端,前端通过em标签即可实现高亮!

也可以自定义标签或者样式

GET /test3/_doc/_search
{
  "query":{
    "match":{
      "name":"李"
    }
  },
  "highlight":{
    "pre_tags":"<i class='key' style='color:red'>",
    "post_tags":"</i>",
    "fields":{
      "name":{}
    }
  }
}

image20221214145525287.png

集成sprigboot(2.2.6)

依赖

<properties>
        <java.version>1.8</java.version>
        <elasticsearch.version>7.6.1</elasticsearch.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
​
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

image20221215092540527.png

注意这里的版本要和本地的版本对应

编写配置类

@Configuration
public class ElasticSearchClientConfig {

    @Bean
    public RestHighLevelClient restHighLevelClient(){
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("localhost", 9200, "http")
                )
        );
        return client;
    }
}

Es的api使用

索引
//添加索引
@SpringBootTest
class StudyEsApplicationTests {
  @Autowired
  @Qualifier("restHighLevelClient")
  RestHighLevelClient client;

  @Test
  void contextLoads() throws IOException {
    //        创建客户端
    CreateIndexRequest createIndexRequest = new CreateIndexRequest("cws_test1");
    //        执行请求,获取请求响应
    CreateIndexResponse createIndexResponse = client.indices().create(createIndexRequest, RequestOptions.DEFAULT);
    System.out.println(createIndexResponse);
  }
}
/**
     * @param
     * @return void
     * @author cws
     * @date 2022/12/15 10:39
     * 判断索引是否存在
     */
@Test
void getText() throws IOException {
  GetIndexRequest getIndexRequest = new GetIndexRequest("cws_test1");
  boolean exists = client.indices().exists(getIndexRequest, RequestOptions.DEFAULT);
  System.out.println(exists);
}
/**
     * 删除索引
     */
@Test
void DelText() throws IOException {
  DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest("cws_test1");
  AcknowledgedResponse delete = client.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);
  System.out.println(delete);
}
文档
//添加文档
@Test
void AddText() throws IOException {
  User user = new User("李四",50);
  //        创建请求  put /cws_test1/_doc/1
  IndexRequest indexRequest = new IndexRequest("cws_test1");
  indexRequest.id("2"); //设置索引id
  indexRequest.timeout(TimeValue.timeValueSeconds(1l));
  //        将数据放入请求
  indexRequest.source(JSONUtil.toJsonStr(user), XContentType.JSON);

  //       使用客户端发送请求
  IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT);
  System.out.println(indexResponse.status().getStatus());
  System.out.println(indexResponse.toString());
}
 /**
     * 删除文档
     */
    @Test
    void del() throws IOException {
        DeleteRequest deleteRequest = new DeleteRequest("cws_test1");
        deleteRequest.id("1");
        DeleteResponse delete = client.delete(deleteRequest, RequestOptions.DEFAULT);
        System.out.println(delete);
    }
/**
     * 获取单个文档
     */
@Test
void getDocument() throws IOException {
  GetRequest getRequest = new GetRequest("cws_test1");
  getRequest.id("2");
  GetResponse documentFields = client.get(getRequest, RequestOptions.DEFAULT);
  System.out.println(documentFields.getSource());
}
/**
     * 修改文档
     */
@Test
void update() throws IOException {
  UpdateRequest updateRequest = new UpdateRequest("cws_test1","2");
  User user = new User("测试",5);
  updateRequest.doc(JSONUtil.toJsonStr(user), XContentType.JSON);
  UpdateResponse response = client.update(updateRequest, RequestOptions.DEFAULT);
  System.out.println(response.status());
}
/**
   * 判断是否文档
*/
@Test
void exitDocument() throws IOException {
  GetRequest getRequest = new GetRequest("cws_test1","2");
  //  设置不返回_source
  getRequest.fetchSourceContext(new FetchSourceContext(false));
  getRequest.storedFields("_none_");
  boolean exists = client.exists(getRequest, RequestOptions.DEFAULT);
  System.out.println(exists);
}

批量添加数据

@Test
void addListOfDocument() throws IOException {
  //批量api
  BulkRequest bulkRequest = new BulkRequest();
  bulkRequest.timeout("10s");

  List<User> list = new ArrayList<>();
  list.add(new User("ls", 1));
  list.add(new User("ws", 2));
  list.add(new User("ww", 3));
  list.add(new User("历史", 4));
  list.add(new User("pp", 5));

  for (int i = 0; i < list.size(); i++) {
    bulkRequest.add(
      new IndexRequest("cws_test1")
      .id("" + (i + 1))
      .source(JSONUtil.toJsonStr(list.get(i)), XContentType.JSON)
    );
  }
  
  BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
  System.out.println(bulk.hasFailures());//是否出现异常? false表示没有出现(成功),
  System.out.println(bulk.status());//状态
}

批量删除

@Test
void dels() throws IOException {
  BulkRequest bulkRequest = new BulkRequest();
  bulkRequest.timeout("10s");

  List<String> list = new ArrayList<>();
  list.add("1");
  list.add("2");
  list.add("3");


  for (int i = 0; i < list.size(); i++) {
    bulkRequest.add( new DeleteRequest("cws_test1")
                    .id(list.get(i)));
  }
  BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
  System.out.println(bulk.status());
}

批量更新,批量删除,批量添加操作基本一致,差别在 bulkRequest.add()传的参数

查询(searchRequest)

构建添加类作用
SearchSourceBuilder条件构造
HighlightBuilder构建高亮
TermsQueryBuilder精确查询
MatchAllQueryBuilder查询全部
以此类推,把索引构建号的xxxQueryBuilder放置sourceBuilder.query()中即可
  @Test
    void search() throws IOException {
//        创建所需api
        SearchRequest searchRequest = new SearchRequest();
        //创建搜索条件
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

//        QueryBuilders.matchAllQuery() 匹配所有
//        使用QueryBuilders工具类快速构建
        TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery("name", "ww");
        sourceBuilder.query(termsQueryBuilder);
        sourceBuilder.timeout(new TimeValue(10l, TimeUnit.SECONDS));

        searchRequest.source(sourceBuilder);
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        System.out.println(JSONUtil.toJsonStr(searchResponse.getHits()));
        for (SearchHit hit : searchResponse.getHits().getHits()) {
            System.out.println(hit.getSourceAsMap());
        }
    }
相当用java构建了

GET /cws_test1/_doc/_search
{
  "query":{
    "term":{#精确匹配
      "name":"ww"
    }
  }
}

image20221215143644540.png

实战(springboot2.2.6)

爬取京东数据,进行es实现练习。

依赖

<properties>
  <java.version>1.8</java.version>
  <elasticsearch.version>7.6.1</elasticsearch.version>
  </properties>
  <dependencies>
  <!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
<dependency>
  <groupId>org.jsoup</groupId>
  <artifactId>jsoup</artifactId>
  <version>1.11.3</version>
  </dependency>
​
​
  <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
  </dependency>
  <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
​
  <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-devtools</artifactId>
  <scope>runtime</scope>
  <optional>true</optional>
  </dependency>
  <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-configuration-processor</artifactId>
  <optional>true</optional>
  </dependency>
  <dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <optional>true</optional>
  </dependency>
  <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
  </dependency>
​
  <dependency>
  <groupId>cn.hutool</groupId>
  <artifactId>hutool-all</artifactId>
  <version>5.7.17</version>
  </dependency>
​
  <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-thymeleaf</artifactId>
  </dependency>
​
  </dependencies>

客户端工具类注意版本匹配

@Configuration
public class ElasticSearchClientConfig {

    @Bean
    public RestHighLevelClient restHighLevelClient(){
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("localhost", 9200, "http")
                )
        );
        return client;
    }
}

爬取数据

//爬取数据类
@Component
public class HtmlParseUtils {
    public List<Title> getDataJD(String keyWord) throws IOException {
        List<Title> list = new ArrayList<>();

        String url = "https://search.jd.com/Search?keyword=" + keyWord;

        //获取整个页面
        Document document = Jsoup.parse(new URL(url), 30000);
//        System.out.println(document);
        //选择所需要的部分
        Element goodsList = document.getElementById("J_goodsList");
//        获取所有的li
        Elements lis = goodsList.getElementsByTag("li");

        for (Element li : lis) {
//            获取图片
            String img = li.getElementsByTag("img").eq(0).attr("data-lazy-img");
//            获取价格
            String price = li.getElementsByClass("p-price").eq(0).text();
//  获取书名
            String bookName = li.getElementsByClass("p-name").eq(0).text();
//            System.out.println("------------------------------------------------------");
//            System.out.println(img);
//            System.out.println(price);
//            System.out.println(bookName);
            Title title = new Title(bookName, price, img);
            list.add(title);
        }
        return list;
    }
}

编写实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Title {

    private String name;

    private String price;

    private String imgUrl;
}

将数据添加到es

@Service
public class ContentService {

  @Autowired
  RestHighLevelClient restHighLevelClient;

  public String addEs(String keyword) throws IOException {
    List<Title> list = new HtmlParseUtils().getDataJD(keyword);
    BulkRequest bulkRequest = new BulkRequest();
    bulkRequest.timeout("10s");
    for (int i = 0; i < list.size(); i++) {
      bulkRequest.add(
        new IndexRequest("cws_jd")
        .source(JSONUtil.toJsonStr(list.get(i)), XContentType.JSON)
      );
    }
    BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
    boolean b = bulk.hasFailures();
    if (!b) {
      return "成功!";
    }
    return "失败";
  }
}

编写service搜索接口

p
ublic List<Map<String,Object>> search(String keyWord,int pageNum,int pageSize) throws IOException {
  if(pageNum==0){
    pageNum=1;
  }
  SearchRequest searchRequest = new SearchRequest();
  SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
  //设置分页
  sourceBuilder.from(pageNum);
  sourceBuilder.size(pageSize);
  //        构建查询条件
  TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery("name", keyWord);
  sourceBuilder.query(termsQueryBuilder);
  searchRequest.source(sourceBuilder);
  SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  //        封装返回的数据
  List<Map<String,Object>> list=new ArrayList<>();
  for (SearchHit hit : search.getHits().getHits()) {
    Map<String, Object> sourceAsMap = hit.getSourceAsMap();
    list.add(sourceAsMap);
  }
  return list;
}

controller

@RestController
@SuppressWarnings("all")
public class ContentController {
    @Autowired
    ContentService contentService;
    @GetMapping("/Content/{keyWord}")
    public String setContent(@PathVariable("keyWord") String keyWord) throws IOException {
        return contentService.addEs(keyWord);
    }
   @GetMapping("/search/{keyWord}/{pageNum}/{pageSize}")
    public List<Map<String,Object>> searchKeyList(
           @PathVariable("keyWord") String keyWord,
           @PathVariable("pageNum") int pageNum,
           @PathVariable("pageSize") int pageSize
   ) throws IOException {
     return   contentService.search(keyWord,pageNum,pageSize);
   }
}

设置高亮

 public List<Map<String, Object>> searchHighlightFields(String keyWord, int pageNum, int pageSize) throws IOException {
        if (pageNum == 0) {
            pageNum = 1;
        }
        SearchRequest searchRequest = new SearchRequest();
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //设置分页
        sourceBuilder.from(pageNum);
        sourceBuilder.size(pageSize);
        //        构建查询条件
        TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery("name", keyWord);
        //设置高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("name");//要高亮的字段名
        highlightBuilder.preTags("<span class='key' style='color:red'>");
        highlightBuilder.postTags("</span>");
        highlightBuilder.requireFieldMatch(false);//取消多个字段显示
        sourceBuilder.highlighter(highlightBuilder);

        sourceBuilder.query(termsQueryBuilder);
        searchRequest.source(sourceBuilder);
        SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//        封装返回的数据
        List<Map<String, Object>> list = new ArrayList<>();
        for (SearchHit hit : search.getHits().getHits()) {
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();//高亮的结果
            HighlightField name = highlightFields.get("name");//获取到高亮的字段
            Map<String, Object> sourceAsMap = hit.getSourceAsMap();//原来的结果
            //如果name不等于空调换数据
            if (name != null) {

                Text[] fragments = name.fragments();
                String new_name = "";
                for (Text text : fragments) {
                    new_name += text;
                }
                sourceAsMap.put("name", new_name);
            }
            list.add(sourceAsMap);
        }
        return list;
    }

集成练习二

数据库结构:

CREATE TABLE `NewTable` (
`id`  bigint(20) NOT NULL COMMENT '酒店id' ,
`name`  varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '酒店名称' ,
`address`  varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '酒店地址' ,
`price`  int(10) NOT NULL COMMENT '酒店价格' ,
`score`  int(2) NOT NULL COMMENT '酒店评分' ,
`brand`  varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '酒店品牌' ,
`city`  varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '所在城市' ,
`star_name`  varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '酒店星级,1星到5星,1钻到5钻' ,
`business`  varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '商圈' ,
`latitude`  varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '纬度' ,
`longitude`  varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '经度' ,
`pic`  varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '酒店图片' ,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8mb4_general_ci
ROW_FORMAT=COMPACT
;

更具数据库结构创建es的mapping

PUT /hotel
{
  "mappings": {
    "properties": {
      "id":{
        "type": "keyword" #在es中id比较特殊,不进行分词所以这样用keyword
      },
      "name":{
        "type": "text",
        "analyzer": "ik_max_word",
        "copy_to": "all"
      },
      "adderss":{
        "type": "text",
         "index": false
      },
      "price":{
        "type": "integer"
      },
      "score":{
        "type": "integer"
      },
      "brand":{
        "type": "keyword",
         "copy_to": "all"
      },
      "city":{
        "type": "keyword"
      },
      "starName":{
        "type": "keyword"
      },
      "business":{
        "type": "keyword"
        , "copy_to": "all"
      },
      "location":{
        "type": "geo_point"
      },
       "pic":{
        "type": "keyword",
       "index": false
      },
      "all":{
        "type": "text",
        "analyzer": "ik_max_word"
      }
    }
  }
}

注明:在上面结构中多出了location和all字段。

location字段主要用于处理地理坐标,那么在es中支持两种坐标数据类型:geo_point: 此类型用于存储地理位置点,通常由经度和纬度组成。一个 geo_point 字段可以以两种方式存储坐标: 作为数组 [lon, lat],其中 lon 是经度,lat 是纬度。作为对象 {"lat": lat, "lon": lon} 或 {"lat": lat, "lon": lon, "geohash": geohash},其中可选的 geohash 是对经纬度进行编码的字符串,便于区间查询。 geo_shape: 这种类型用于存储更复杂的地理形状,如多边形、圆形、线等。它支持对地理空间区域进行索引和查询,适用于处理区域覆盖、邻近判断等复杂的空间关系查询。

all字段用于搜索,在实现中我们想通过酒店名称、酒店品牌和商圈同时去搜索,那么通过单个字段是实现不了的,所以es提供了copy_to功能,将需要的字段全部拷贝到一个字段中,我们通过这个字段去进行检索即可。

接入RestClient

注意自己的es版本,我使用的是7.12.1

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.12.1</version>
</dependency>
   <properties>
        <java.version>1.8</java.version>
        <elasticsearch.version>7.12.1</elasticsearch.version>
    </properties>

创建索引库

@SpringbootTest
public class RestClientTest {
    private RestHighLevelClient client;

    @Test
  void testCreateHotelIndex() throws IOException {
//  创建request对象
        CreateIndexRequest request = new CreateIndexRequest("hotel");
        request.source(HOTEL_TEMP, XContentType.JSON);
        client.indices().create(request, RequestOptions.DEFAULT);
    }

    @BeforeEach
    void setUp()
    {
         this.client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://192.168.88.102:9200")
        ));
    }

    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }
}
package cn.itcast.hotel.estemp;

public class IndexTemplate {

    public static final String HOTEL_TEMP = "{\n" +
            "  \"mappings\": {\n" +
            "    \"properties\": {\n" +
            "      \"id\":{\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"name\":{\n" +
            "        \"type\": \"text\",\n" +
            "        \"analyzer\": \"ik_max_word\",\n" +
            "        \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"adderss\":{\n" +
            "        \"type\": \"text\",\n" +
            "         \"index\": false\n" +
            "      },\n" +
            "      \"price\":{\n" +
            "        \"type\": \"integer\"\n" +
            "      },\n" +
            "      \"score\":{\n" +
            "        \"type\": \"integer\"\n" +
            "      },\n" +
            "      \"brand\":{\n" +
            "        \"type\": \"keyword\",\n" +
            "         \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"city\":{\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"starName\":{\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"business\":{\n" +
            "        \"type\": \"keyword\"\n" +
            "        , \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"location\":{\n" +
            "        \"type\": \"geo_point\"\n" +
            "      },\n" +
            "       \"pic\":{\n" +
            "        \"type\": \"keyword\",\n" +
            "       \"index\": false\n" +
            "      },\n" +
            "      \n" +
            "      \"all\":{\n" +
            "        \"type\": \"text\",\n" +
            "        \"analyzer\": \"ik_max_word\"\n" +
            "      }\n" +
            "    }\n" +
            "  }\n" +
            "}";
}

使用RestClient操作文档(CRUD)

添加文档

 @Test
    void setHotelOfDoc() throws IOException {
//          put /hotel/_doc/36934
        IndexRequest indexRequest = new IndexRequest("hotel");
        indexRequest.timeout("1s");//设置超时时间
        Hotel hotel = hotelService.getById(36934L);
        indexRequest.id(hotel.getId().toString());//设置id
        HotelDoc hotelDoc = new HotelDoc(hotel);
        indexRequest.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
        client.index(indexRequest, RequestOptions.DEFAULT);
    }

查询文档

 /**
     * 获取文档
     * @throws IOException
     */
    @Test
    void testGetHotelOfDoc() throws IOException {
//        get /hotel/_doc/36934
        GetRequest getRequest = new GetRequest();
        getRequest.index("hotel");
        getRequest.id("36934");
        GetResponse documentFields = client.get(getRequest, RequestOptions.DEFAULT);
        String sourceAsString = documentFields.getSourceAsString();
        System.out.println(sourceAsString);
    }

修改文档

/**
     * 修改文档
     * @throws IOException
     */
    @Test
    void testUpdateHotelOfDoc() throws IOException {
        UpdateRequest updateRequest = new UpdateRequest();
        updateRequest.index("hotel");
        updateRequest.id("36934");
        updateRequest.doc("price", 200,
                "brand","8天"
                );
        client.update(updateRequest, RequestOptions.DEFAULT);
    }
//这里是用的部分修改,其实也可以使用client.index()进行修改

删除文档

  /**
     * 删除文档
     * @throws IOException
     */
    @Test
    void testDelHotelOfDoc() throws IOException {
        DeleteRequest deleteRequest = new DeleteRequest();
        deleteRequest.id("36934");
        deleteRequest.index("hotel");
        client.delete(deleteRequest, RequestOptions.DEFAULT);
    }

批量导入文档

@Test
    void addListOfDocument() throws IOException {
        //批量api
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout("10s");
        List<HotelDoc> list = hotelService.list().stream().map(hotel -> {
            HotelDoc hotelDoc = new HotelDoc(hotel);
            return hotelDoc;
        }).collect(Collectors.toList());
        for (int i = 0; i < list.size(); i++) {
            bulkRequest.add(
                    new IndexRequest("hotel")
                            .id(list.get(i).getId().toString())
                            .source(JSON.toJSONString(list.get(i)), XContentType.JSON)
            );
        }
        BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
        System.out.println(bulk.hasFailures());//是否出现异常? false表示没有出现(成功),
        System.out.println(bulk.status());//状态
    }

DSL查询语法

ElasticSearch提供了基于JSON来定义的查询。常见包括:

查询所有

:查询出所有数据,一般测试用。例如:match_all

查询所有

get /hotel/_search
{
  "query":{
    "match_all": {}
  }
}
全文检索(fuul text) 查询

利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:

  • match_query

  • multi_match_query

全文检索查询

#推荐,上面我们将字段全部copy到了all这个字段中,所以两个查询是一样的,但是效率不同
get /hotel/_search
{
  "query":{
    "match": {
      "all": "如家北京"
    }
  }
}
#效率低字段较多
get /hotel/_search
{
  "query":{
    "multi_match": {
      "query": "如家北京",
      "fields": ["name","brand","business"]
    }
  }
}
精确查询

:根据精确词条值查找数据,一般是查找keyword、数值、boolean等类型的字段。例如:

  • ids

  • range

  • term

精确查询

#查询为city为北京的酒店
get /hotel/_search
{
  "query":{
    "term": {
      "city": {
        "value": "北京"
      }
    }
  }
}
# 查询price在大于等于100小于等于2000
get /hotel/_search
{
  "query":{
    "range": {
      "price": {
        "gte": 100,
        "lte": 2000
      }
    }
  }
}
地理(geo)查询

:根据精确维度查询。例如:

  • geo_distance

  • geo_bounding_box

get /hotel/_search
{
  "query":{
    "geo_distance":{
      "distance":"3km", #范围
      "location":"31.21,121.5" #自己所在位置的地理坐标
    }
  }
}
复合查询

:复合查询可以将上述各种查询条件组合起来,合并查询条件。例如:

  • bool

  • function_score

function_score查询

应用场景:例如百度为什么查询企业出来的这个企业会排第一条呢?首先一定匹配查询的结果,然后有些企业花钱让你查的数据的score的评分较高。

案例:

get /hotel/_search
{
  "query":{
    "function_score": {
      "query": {
        "match": {
          "all": "外滩"
        }
      }
    }
    }
}

此时我们想让最后这家的排名靠前就可以如下操作:

get /hotel/_search
{
  "query":{
    "function_score": {
      "query": {
        "match": {
          "all": "外滩"
        }
      },
      "functions": [
        {
          "filter": {
            "term": {
              "brand": "如家"
            }
          },
          "weight": 10
        }
      ]
    }
    }
}

默认是乘于weight,那么就是38,还可以通过boost_mode控制:

get /hotel/_search
{
  "query":{
    "function_score": {
      "query": {
        "match": {
          "all": "外滩"
        }
      },
      "functions": [
        {
          "filter": {
            "term": {
              "brand": "如家"
            }
          },
          "weight": 10
        }
      ],
      "boost_mode": "sum" #这里是相加,可以有很多参数,可见上图
    }
    }
}
bool查询

布尔查询是一个或者多个查询子句的组合。方式有:

  • must:必须匹配每个子查询,类似“与”

  • should:选择性匹配子查询,类型“或”

  • must_not:必须不匹配,不参与算分,类"非"

  • filter:必须匹配,不参与算分

案例

#查询名称包含如家,价格不高于400,在坐标31.21,121.5周围10km范围内的酒店。
get /hotel/_search
{
  "query":{
    "bool": {
      "must": [
        {
          "match": {
            "name":"如家"
          }
        }
      ],
     "must_not": [
       {"range": {
         "price": {
           "gt": 400
         }
       }}
     ],
     "filter": [
       {
         "geo_distance": {
           "distance": "10km",
           "location": "31.21,121.5"
         }
       }
     ]
    }
  }
}
排序

例子:在我们选择酒店是我们可以看到酒店可我们的距离,以升序排,我们就可以在es中使用sort进行操作。

get /hotel/_search
{
  "query":{
    "match_all": {}
  },
  "sort": [
{
 "_geo_distance":{
   "location":{
     "lat":31.034, #纬度
     "lon":121.612 #经度
   },
   "order":"asc",
   "unit":"km"
 }
}
    ]
}

使用RestClient操作DSL

RestClient查询所有
get /hotel/_search
{
    "query":{
      "match_all": {}
    },
    "from":1,
    "size":50
}


  /**
     * 查询所有数据
     * @throws IOException
     */
    @Test
    void testSearchAll() throws IOException {
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotel");
        // 2.准备请求参数
        request.source().query(QueryBuilders.matchAllQuery()).size(50);
        search(request);
    }

    private void search(SearchRequest request) throws IOException {
        // 3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 4.处理响应
        SearchHits hits = response.getHits();
        long value = hits.getTotalHits().value;
        System.out.println(value);
        SearchHit[] hits1 = hits.getHits();
        for (SearchHit hit : hits1) {
            HotelDoc hotelDoc = JSON.parseObject(hit.getSourceAsString(), HotelDoc.class);
            System.out.println(hotelDoc);
        }
    }
RestClient全文检索
/**
     * 全文检索 matchQuery
     * @param
     * @throws IOException
     get /hotel/_search
{
  "query":{
    "match": {
      "all": "如家"
    }
  }
}
     */
    @Test
    void testMatchQuery() throws IOException {
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotel");
        // 2.准备请求参数
        request.source().query(QueryBuilders.matchQuery("all", "如家"));
        search(request);
    }


    /**
     * 全文检索 multi_match_query
     * @param
     * @throws IOException
     
     get /hotel/_search
{
  "query":{
    "multi_match": {
      "query": "如家",
      "fields": ["name","brand"]
    }
  }
}
     */
    @Test
    void testMultiMatchQueryQuery() throws IOException {
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotel");
        // 2.准备请求参数
        request.source().query(QueryBuilders.multiMatchQuery("如家", "name", "brand"));
        search(request);
    }
RestClient精确查询(term,range)
 /**
     * 精确查询
     * @param
     * @throws IOException
     */
    @Test
    void testTermQuery() throws IOException {
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotel");
        // 2.准备请求参数
//        request.source().query(QueryBuilders.termQuery("city", "上海"));
        request.source().query(QueryBuilders.rangeQuery("price").lt(300));
        search(request);
    }
RestClient地理查询
 /**
     * 精确查询
     * @param
     * @throws IOException
     */
    @Test
    void testGeoDistanceQuery() throws IOException {
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotel");
        // 2.准备请求参数
        request.source().query(QueryBuilders.geoDistanceQuery("location").distance("50km").point(31.197804,121.498618));
        search(request);
    }
RestClient复合查询
/**
     * 复合查询
     * @param
     * @throws IOException
     */
    @Test
    void testBoolQueryBuilder() throws IOException {
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotel");
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        boolQuery.must(QueryBuilders.termQuery("city", "上海"));
        boolQuery.filter(QueryBuilders.rangeQuery("price").lte(200));
        request.source().query(boolQuery);
        search(request);
    }
RestClient排序与分页
  /**
     * 排序与分页
     * @param
     * @throws IOException
     */
    @Test
    void testSortAndPage() throws IOException {
        // 1.准备Request
        SearchRequest searchRequest = new SearchRequest("hotel");
        searchRequest.source().query(QueryBuilders.matchAllQuery()).from(1).size(10)
                .sort("price", SortOrder.DESC);
        search(searchRequest);
    }
RestClient(词语高亮)

在默认情况下检索字段需要和高亮字段一致,如果碰到特殊情况可以使用require_field_match改为false

 /**
     * 代码高亮
     * @param
     * @throws IOException
     */
    @Test
    void testHighlighter() throws IOException {
        // 1.准备Request
        SearchRequest searchRequest = new SearchRequest("hotel");
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("name");
        highlightBuilder.preTags("<font color='red'>");
        highlightBuilder.postTags("</font>");
        highlightBuilder.requireFieldMatch(false);
        searchRequest.source().query(QueryBuilders.matchQuery("all", "如家")).highlighter(highlightBuilder);
        SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
        search.getHits().forEach(hit -> {
            System.out.println(hit.getHighlightFields());
        });
    }

聚合

聚合可以实现对文档数据的统计、分析、运算。常见的三类:

桶(Bucket)聚合:用来对文档做分组

  • TermAggregation:按照文档字段值分组

  • Date Histogram:按照日期阶梯,例如一周为一组或者一月为一组

度量(Metric)聚合:用以计算一些值、比如:最大值、最小值、平均值等

  • Avg:求平均值

  • Max:求最大值

  • Min:最小值

  • Stats:同时求max、min、avg、sum等

管道(pipeline)聚合:其他聚合的结果为基础做聚合

桶聚合Bucket

get  /hotel/_search
{
  "size":0,
  "aggs":{
    "brandAgg":{ #设置聚合名称
      "terms": {
        "field": "brand", #选择聚合字段
        "size": 20 #页数
      }
    }
  }
}
#类似mysql group by

在上图中我们可以看出分组对的文档数量是按照降序排序的,我们可以进行修改:

get  /hotel/_search
{
  "size":0,
  "aggs":{
    "brandAgg":{
      "terms": {
        "order": {
          "_count": "asc"  #更具count数量升序排
        }, 
        "field": "brand",
        "size": 20
      }
    }
  }
}

我可也可以加上query条件,锁定范围:

get  /hotel/_search
{
  "query":{
    "range": {
      "price": {
        "lte": 200
      }
    }
  },
  "size":0,
  "aggs":{
    "brandAgg":{
      "terms": {
        "order": {
          "_count": "asc"
        }, 
        "field": "brand",
        "size": 20
      }
    }
  }
}

度量(Metric)聚合

#查询每种牌子的酒店的min、max、avg,并通过平均分排序
GET /hotel/_search
{
  "size": 0,
  "aggs": {
    "brandAgg": { #结合bucket聚合
      "terms": {
        "order": { 
          "scoreAgg.avg": "asc" 
        }, 
        "field": "brand",
        "size": 20
      },
      "aggs": {
        "scoreAgg": { #Metric自定义名称
          "stats": { #这个的stats就包含了min、max、avg
            "field": "score" #Metric聚合字段
          }
        }
      }
    }
  }
}
#类似数据库的 先分组然后再求聚合

RestClient操作聚合

   /**
     * 聚合操作
     */
    @Test
    void testAggregation() throws IOException {
        SearchRequest request = new SearchRequest("hotel");
//        不需要具体内容
        request.source().size(0);
        request.source().aggregation(
                AggregationBuilders.terms("brandAgg")
                        .field("brand")
                        .size(20)
                        .order(BucketOrder.aggregation("_count", false))
        );
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        Aggregations aggregations = response.getAggregations();
        Terms brandAgg = aggregations.get("brandAgg");
        List<? extends Terms.Bucket> buckets = brandAgg.getBuckets();
        buckets.forEach(bucket -> {
            String keyAsString = bucket.getKeyAsString();
            System.out.println(keyAsString);
        });
    }

自动补全

要实现根据字母做补全,就必须对文档按照拼音分词。在GitHub上恰好有elasticsearch的拼音分词插件。地址:GitHub - infinilabs/analysis-pinyin: 🛵 This Pinyin Analysis plugin is used to do conversion between Chinese characters and Pinyin.

在京东等平台搜索框输入拼音或者关键子会有提示效果那么大多数都是基于es的自动补全实现的。这里需要拼音分词器插件:

拼音分词器安装

将py文件夹上传到es映射的目录下和ik分词器一样的方法。也可以自己解压elasticsearch-analysis-pinyin-7.12.1.zip命名为py。

测试:

自定义分词器

为什么要自定义分词器呢?如果单独使用ik或者拼音分词器并不能满足我们的需求,例如我使用拼音分词器那么结果只有拼音结果没有中文结果且拼音首字母拼接,这样无法满足我们的搜索。

自定义使用

主要:需要在创建索引时指定

PUT /test
{
  "settings": {
    "analysis": {
      "analyzer": { 
        "my_analyzer": { 
          "tokenizer": "ik_max_word",
          "filter": "py"
        }
      },
      "filter": {
        "py": { 
          "type": "pinyin",
          "keep_full_pinyin": false,
          "keep_joined_full_pinyin": true,
          "keep_original": true,
          "limit_first_letter_length": 16,
          "remove_duplicated_term": true,
          "none_chinese_pinyin_tokenize": false
        }
      }
    }
  },
  "mappings": { 
    "properties": {
      "title":{ #指定字段
        "type":"text",
        "analyzer": "my_analyzer", #调用自定义分词器
         "search_analyzer": "ik_smart" #搜索时使用 ik
      }
    }
  }
}

解释:

PUT /test 这是一个HTTP的PUT请求,目标是Elasticsearch中的一个索引,这里索引名为test。这意味着你正在更新或创建名为test的索引,并为其指定特定的设置。 请求体内容详解 settings:这部分包含索引的配置信息,重点在于定义分析器设置。 analysis:分析模块的配置,用于定义如何对文本进行分析处理,包括分词、过滤等。 analyzer: "my_analyzer" 定义了一个名为my_analyzer的自定义分析器。分析器决定了如何将文本分解成词语(tokens)以便于搜索和索引。 tokenizer: "ik_max_word" 指定了分析器使用的分词器为ik_max_word。IK分词器是针对中文文本设计的,ik_max_word模式会尽可能地进行词语切分,生成最细粒度的词语组合。 filter: "py" 定义了一个名为py的过滤器,该过滤器应用于分词结果。这里的py是一个拼音过滤器,用于生成中文字符的拼音表示。 py过滤器的参数说明: keep_full_pinyin: 设为false表示不保留全拼形式。 keep_joined_full_pinyin: 设为true表示保留连接的全拼音形式,比如"zhongguo"。 keep_original: 设为true表示保留原始的中文字符。 limit_first_letter_length: 设置首字母拼音的最大长度为16,超过的会被截断。 remove_duplicated_term: 设为true表示移除重复的拼音项,避免索引膨胀。 none_chinese_pinyin_tokenize: 设为false表示不对非中文字符进行拼音处理。

测试

POST /test/_analyze
{
 "analyzer": "my_analyzer",
 "text": ["应该出现张三的拼音"]
}

自动补全类型及查询功能(Completion Suggester)

官网地址:Suggesters | Elasticsearch Guide [7.6] | Elastic

elasticsearch提供了Completion Suggester查询来实现自动补全功能。这个查询会匹配以用户输入内容开头的词条并返回。为了提高补全查询的效率,对于文档中字段的类型有一些约束:参与补全查询的字段必须是completion类型。字段的内容一般是用来补全的多个词条形成的数组。

案例

// 自动补全的索引库
PUT test2
{
  "mappings": {
    "properties": {
      "title":{
        "type": "completion"
      }
    }
  }
}
// 示例数据
POST test2/_doc
{
  "title": ["Sony", "WH-1000XM3"]
}
POST test2/_doc
{
  "title": ["SK-II", "PITERA"]
}
POST test2/_doc
{
  "title": ["Nintendo", "switch"]
}

// 自动补全查询
POST /test2/_search
{
  "suggest": {
    "title_suggest": {
       "text": "s", // 关键字
      "completion": {
        "field": "title", // 补全字段
        "skip_duplicates": true, // 跳过重复的
        "size": 10 // 获取前10条结果
      }
    }
  }
}

注意:自动补全的类型必须是completion

综合案例

我们基于集成练习二里面的数据库结构做个自动补全的综合案例。

1.修改索引

// 创建名为"hotel"的Elasticsearch索引,并定义其设置和映射规则。
PUT /hotel
{
  "settings": {
    "analysis": { // 分析器与过滤器配置区域
      "analyzer": { // 自定义分析器定义
        "text_anlyzer": { // 用于全文本分析的分析器
          "tokenizer": "ik_max_word", // 使用IK分词器的最细粒度分词模式
          "filter": "py" // 应用自定义的拼音过滤器
        },
        "completion_analyzer": { // 专用于自动补全字段的分析器
          "tokenizer": "keyword", // 使用keyword分词器,保持整个输入作为单个token
          "filter": "py" // 同样应用拼音过滤器以支持拼音补全
        }
      },
      "filter": { // 自定义过滤器定义
        "py": { // 拼音过滤器配置
          "type": "pinyin", // 指定过滤器类型为拼音处理
          "keep_full_pinyin": false, // 不保留全拼形式
          "keep_joined_full_pinyin": true, // 保留连接的全拼音形式
          "keep_original": true, // 保留原始中文字符
          "limit_first_letter_length": 16, // 首字母拼音最大长度限制
          "remove_duplicated_term": true, // 移除重复的拼音项
          "none_chinese_pinyin_tokenize": false // 对非中文字符不进行拼音处理
        }
      }
    }
  },
  "mappings": { // 映射定义,描述文档字段的数据类型和属性
    "properties": { // 具体字段定义
      "id": { // 唯一标识符
        "type": "keyword" // 关键词类型,适合唯一ID
      },
      "name": { // 酒店名称
        "type": "text", // 文本类型,用于全文搜索
        "analyzer": "text_anlyzer", // 搜索时使用自定义的text_anlyzer
        "search_analyzer": "ik_smart", // 查询时使用ik_smart分词器,支持智能分词
        "copy_to": "all" // 将此字段内容复制到"all"字段,便于综合搜索
      },
      "address": { // 地址信息
        "type": "keyword", // 作为关键词存储,不参与全文搜索
        "index": false // 不建立索引,提高写入性能
      },
      "price": { // 价格
        "type": "integer" // 整型数据
      },
      "score": { // 评分
        "type": "integer"
      },
      "brand": { // 品牌
        "type": "keyword",
        "copy_to": "all" // 同样复制到"all"字段
      },
      "city": { // 所在城市
        "type": "keyword"
      },
      "starName": { // 星级名称
        "type": "keyword"
      },
      "business": { // 商圈
        "type": "keyword",
        "copy_to": "all"
      },
      "location": { // 地理位置坐标
        "type": "geo_point"
      },
      "pic": { // 图片链接
        "type": "keyword",
        "index": false // 图片链接不需要搜索,故不建立索引
      },
      "all": { // 综合全文搜索字段
        "type": "text",
        "analyzer": "text_anlyzer",
        "search_analyzer": "ik_smart"
      },
      "suggestion": { // 自动补全字段
          "type": "completion", // 使用completion类型
          "analyzer": "completion_analyzer" // 应用专为补全设计的分析器
      }
    }
  }
}

2.修改构造器

@Data
@NoArgsConstructor
public class HotelDoc {
    private Long id;
    private String name;
    private String address;
    private Integer price;
    private Integer score;
    private String brand;
    private String city;
    private String starName;
    private String business;
    private String location;
    private String pic;
    private Object distance;
    private Boolean isAD;
    private List<String> suggestion;

    public HotelDoc(Hotel hotel) {
        this.id = hotel.getId();
        this.name = hotel.getName();
        this.address = hotel.getAddress();
        this.price = hotel.getPrice();
        this.score = hotel.getScore();
        this.brand = hotel.getBrand();
        this.city = hotel.getCity();
        this.starName = hotel.getStarName();
        this.business = hotel.getBusiness();
        this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
        this.pic = hotel.getPic();
        //这里是应该我们是数据中有/我们做了下处理,这里主要将brand和business加到自动补全自动中
        if(this.business.contains("/")){
            String[] arr = this.business.split("/");
            this.suggestion=new ArrayList<>();
            this.suggestion.add(this.brand);
            Collections.addAll(this.suggestion, arr);
        }else{
            this.suggestion= Arrays.asList(this.brand,this.business);
        }
    }
}

3.重新同步数据库数据

 @Test
    void addListOfDocument() throws IOException {
        //批量api
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout("10s");
        List<HotelDoc> list = hotelService.list().stream().map(hotel -> {
            HotelDoc hotelDoc = new HotelDoc(hotel);
            return hotelDoc;
        }).collect(Collectors.toList());
        for (int i = 0; i < list.size(); i++) {
            bulkRequest.add(
                    new IndexRequest("hotel")
                            .id(list.get(i).getId().toString())
                            .source(JSON.toJSONString(list.get(i)), XContentType.JSON)
            );
        }
        BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
        System.out.println(bulk.hasFailures());//是否出现异常? false表示没有出现(成功),
        System.out.println(bulk.status());//状态
    }

 

4.测试

POST /hotel/_search
{
  "suggest": {
    "title_suggest": {
       "text": "w",
      "completion": {
        "field": "suggestion", 
        "skip_duplicates": true, 
        "size": 10 
      }
    }
  }
}

 

4.RestCline操作自动补全

/**
 * 自动补全
 */
@Test
void testSuggest() throws IOException {
    SearchRequest request = new SearchRequest("hotel");
    request.source().suggest(
            new SuggestBuilder().addSuggestion(
                    "MySuggestion",
                    SuggestBuilders.completionSuggestion("suggestion")
                            .prefix("h")
                            .skipDuplicates(true)
                            .size(10)
            )
    );
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    Suggest suggest = response.getSuggest();
    Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> suggestion = suggest.getSuggestion("MySuggestion");
    suggestion.forEach(entry -> {
        entry.getOptions().forEach(option -> {
            String text = option.getText().string();
            System.out.println(text);
        });
    });
}

微服务ES数据同步

方案一

 

缺陷:冗余,无法保证一定成功,耦合性差,性能差。

方案二

这里我们使用方案二实现

@Component
public class MqGeneratorOfEsData {
    @Resource
    private RabbitTemplate rabbitTemplate;
    public void testSendMessageTopicQueue(String exchange, String routingKey, String message)  {
        rabbitTemplate.convertAndSend(exchange,routingKey,message);
    }
}
//后台crud是发送消息
​
    @PostMapping
    public void saveHotel(@RequestBody Hotel hotel){
        hotelService.save(hotel);
        mqGeneratorOfEsData.testSendMessageTopicQueue("hotel.topic", "hotel.addOrUpdate", hotel.getId().toString());
    }
​
    @PutMapping()
    public void updateById(@RequestBody Hotel hotel){
        if (hotel.getId() == null) {
            throw new InvalidParameterException("id不能为空");
        }
        hotelService.updateById(hotel);
        mqGeneratorOfEsData.testSendMessageTopicQueue("hotel.topic", "hotel.addOrUpdate", hotel.getId().toString());
    }
​
    @DeleteMapping("/{id}")
    public void deleteById(@PathVariable("id") Long id) {
        hotelService.removeById(id);
        mqGeneratorOfEsData.testSendMessageTopicQueue("hotel.topic", "hotel.del", id.toString());
    }
//es服务去处理
@Component
public class MqListenerEsData {
    @Resource
    private RabbitTemplate rabbitTemplate;
    @Resource
    private RestHighLevelClient client;
    @Resource
    IHotelService iHotelService;
    /**
     *  监听新增或者修改
     * @param msg
     * @throws InterruptedException
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "hotel.addOrUpdate"),
            exchange = @Exchange(name = "hotel.topic", type = ExchangeTypes.TOPIC),
            key = {"hotel.addOrUpdate"}
    ))
    public void listenTopicQueue1(String msg) throws InterruptedException {
        try {
            System.out.println("hotel.addOrUpdate接收到消息:" + msg);
            Long id = Long.valueOf(msg);
            Hotel hotel = iHotelService.getById(id);
            HotelDoc hotelDoc = new HotelDoc(hotel);
            IndexRequest indexRequest = new IndexRequest("hotel");
            indexRequest.timeout("1s");//设置超时时间
            indexRequest.id(hotel.getId().toString());//设置id
            indexRequest.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
            IndexResponse index = client.index(indexRequest, RequestOptions.DEFAULT);
            if (index.getResult().name().equals("CREATED")) {
                System.out.println("新增成功");
            } else if (index.getResult().name().equals("UPDATED")) {
                System.out.println("修改成功");
            }
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("保存数据失败");
        }
    }
    /**
     *  删除
     * @param id
     * @throws InterruptedException
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "hotel.del"),
            exchange = @Exchange(name = "hotel.topic", type = ExchangeTypes.TOPIC),
            key = {"hotel.del"}
    ))
    public void listenTopicQueue2(String id) throws InterruptedException {
        try {
            DeleteRequest deleteRequest = new DeleteRequest();
            deleteRequest.id(id);
            deleteRequest.index("hotel");
            client.delete(deleteRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("删除失败");
        }
    }
}

ES集群

因为设备原因这里我们在单台机器上搭建集群。

首先编写一个docker-compose文件,内容如下:

version: '2.2'
services:
  es01:
    image: elasticsearch:7.12.1
    container_name: es01
    environment:
      - node.name=es01
      - cluster.name=es-docker-cluster
      - discovery.seed_hosts=es02,es03
      - cluster.initial_master_nodes=es01,es02,es03
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    volumes:
      - data01:/usr/share/elasticsearch/data
    ports:
      - 9200:9200
    networks:
      - elastic
  es02:
    image: elasticsearch:7.12.1
    container_name: es02
    environment:
      - node.name=es02
      - cluster.name=es-docker-cluster
      - discovery.seed_hosts=es01,es03
      - cluster.initial_master_nodes=es01,es02,es03
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    volumes:
      - data02:/usr/share/elasticsearch/data
    ports:
      - 9201:9200
    networks:
      - elastic
  es03:
    image: elasticsearch:7.12.1
    container_name: es03
    environment:
      - node.name=es03
      - cluster.name=es-docker-cluster
      - discovery.seed_hosts=es01,es02
      - cluster.initial_master_nodes=es01,es02,es03
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    volumes:
      - data03:/usr/share/elasticsearch/data
    networks:
      - elastic
    ports:
      - 9202:9200
volumes:
  data01:
    driver: local
  data02:
    driver: local
  data03:
    driver: local
​
networks:
  elastic:
    driver: bridge

es运行需要修改一些linux系统权限,修改/etc/sysctl.conf文件

vi /etc/sysctl.conf

添加下面的内容:

vm.max_map_count=262144

然后执行命令,让配置生效:

sysctl -p

通过docker-compose启动集群:

docker-compose up -d

集群状态监控

kibana可以监控es集群,不过新版本需要依赖es的x-pack 功能,配置比较复杂。

这里推荐使用cerebro来监控es集群状态,官方网址:https://github.com/lmenezes/cerebro

解压后进入bin目录双击cerebro.bat即可,访问http://localhost:9000/#!/connect即可。

 

输入你的elasticsearch的任意节点的地址和端口,点击connect即可:

绿色的条,代表集群处于绿色(健康状态)。

分片

为了数据的原子性,我们把各个es的数据存放在不用的es上例如:es01可以存放es02的数据,当es02宕机了那么额可以利用es01这台恢复数据。

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1890662.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深入了解激光粒度分析仪:检测物质粒度分布的利器

在科研、工业生产以及环境监测等多个领域中&#xff0c;精确测量物质粒度分布是确保产品质量、研究准确性和环境安全的重要步骤。 近年来&#xff0c;激光粒度分析仪以其独特的技术优势&#xff0c;在这些领域发挥着越来越重要的作用。 在这篇文章中&#xff0c;佰德将带您了…

人工智能--图像语义分割

个人主页&#xff1a;欢迎来到 Papicatch的博客 课设专栏 &#xff1a;学生成绩管理系统 专业知识专栏&#xff1a;专业知识 ​ 文章目录 &#x1f349;引言 &#x1f349;介绍 &#x1f348;工作原理 &#x1f34d;数据准备 &#x1f34d;特征提取 &#x1f34d;像素分…

1.2 ROS2安装

1.2.1 安装ROS2 整体而言&#xff0c;ROS2的安装步骤不算复杂&#xff0c;大致步骤如下&#xff1a; 准备1&#xff1a;设置语言环境&#xff1b;准备2&#xff1a;启动Ubuntu universe存储库&#xff1b;设置软件源&#xff1b;安装ROS2&#xff1b;配置环境。 请注意&…

linux命令行操作

一、看二进制文件 od -t x1 1.txt | less 二、看信号 kill -l man 7 signal 三、查看当前进程的pid号 echo $$

统一开放平台实现方案(访微信SDK)

需求分析 在互联中&#xff0c;我们的服务是不对外开放的&#xff0c;但是有先场景下我们可以对外开放&#xff0c;但是必须是系统所允许的用户才可以&#xff0c;这样做一方面保证安全&#xff0c;另一方面可以提升平台的能力&#xff0c;比如调用微信的接口必须要进行微信开…

如何快速掌握一门编程语言

学习一门新的编程语言可能是一个具有挑战性的过程&#xff0c;但通过一些系统的方法&#xff0c;可以大大加快这个过程。 目录 第一步&#xff1a;通过书籍和视频课程掌握基本语法1. **学习编程语言的基础知识**2. **掌握字符串处理**3. **掌握正则表达式和解析器**4. **掌握面…

AnyView 对 SwiftUI 性能的影响

文章目录 前言测试设置动画卡顿浏览数据没有 AnyView 有 AnyView在浏览数据时修改没有 AnyView 有 AnyView分析结果总结 前言 AnyView 是一种类型擦除的视图&#xff0c;对于 SwiftUI 容器中包含的异构视图非常方便。在这些情况下&#xff0c;你不需要指定视图层次结构中所有视…

【大数据】—美国交通事故分析(2016 年 2 月至 2020 年 12 月)

引言 在当今快速发展的数字时代&#xff0c;大数据已成为我们理解世界、做出决策的重要工具。特别是在交通安全领域&#xff0c;大数据分析能够揭示事故模式、识别风险因素&#xff0c;并帮助制定预防措施&#xff0c;从而挽救生命。本文将深入探讨2016年2月至2020年12月期间&…

西南交通大学【算法分析与设计实验5】

有障碍物的不同路径数量 实验目的 &#xff08;1&#xff09;理解动态规划算法的求解过程。 &#xff08;2&#xff09;分析动态规划算法的时间复杂度&#xff0c;比较动态规划算法与其他算法的效率差异。 &#xff08;3&#xff09;学会如何利用动态规划算法求解具体问题&…

汇聚荣拼多多电商哪些热词比较受关注?

汇聚荣拼多多电商哪些热词比较受关注?在探讨拼多多电商平台的热点关键词时&#xff0c;我们首先得明确&#xff0c;这个平台因其独特的商业模式和市场定位&#xff0c;吸引了大量消费者的目光。拼多多通过“拼团”购物的方式迅速崛起&#xff0c;成为电商行业的一个重要力量。…

5% 消耗,6 倍性能:揭秘新一代 iLogtail SPL 日志处理引擎与 Logstash 的 PK

作者&#xff1a;阿柄 引言 在当今数据驱动的时代&#xff0c;日志收集和处理工具对于保障系统稳定性和优化运维效率至关重要。随着企业数据量的不断增加和系统架构的日益复杂&#xff0c;传统日志处理工具面临着性能、灵活性和易用性等多方面的挑战。Logstash 作为一款广受欢…

qt6 通过http查询天气的实现

步骤如下&#xff1a; cmakelist 当中&#xff0c;增加如下配置 引入包 访问远端api 解析返回的数据 cmakelist 当中&#xff0c;增加如下配置&#xff0c;作用是引入Network库。 引入包 3、访问远端api void Form1::on_pushButton_clicked() {//根据URL(http://t.weather.…

GoLand 2024 for Mac GO语言集成开发工具环境

Mac分享吧 文章目录 效果一、下载软件二、开始安装1、双击运行软件&#xff08;适合自己的M芯片版或Intel芯片版&#xff09;&#xff0c;将其从左侧拖入右侧文件夹中&#xff0c;等待安装完毕2、应用程序显示软件图标&#xff0c;表示安装成功3、打开访达&#xff0c;点击【文…

Flask 数据创建时出错

当我们在使用 Flask 创建数据时遇到错误&#xff0c;可能有多种原因&#xff0c;包括代码错误、数据库配置问题或依赖项错误。具体情况我会总结成一篇文章记录下&#xff0c;主要是归类总结一些常见的解决方法和调试步骤&#xff0c;帮助大家解决问题&#xff1a; 1、问题背景 …

013、MongoDB常用操作命令与高级特性深度解析

目录 MongoDB常用操作命令与高级特性深度解析 1. 数据库操作的深入探讨 1.1 数据库管理 1.1.1 数据库统计信息 1.1.2 数据库修复 1.1.3 数据库用户管理 1.2 数据库事务 2. 集合操作的高级特性 2.1 固定集合(Capped Collections) 2.2 集合验证(Schema Validation) 2.…

如何批量创建、提取和重命名文件夹!!!

你是否还在一个一个手动创建文件名&#xff01; 你是否还在一个一个手动提取文件名&#xff01; 你是否还在一个一个手动修改文件名&#xff01; 请随小生一起批量自动创建、提取、重命名&#xff01; 1、批量创建文件夹 【案例】创建1日-31日共31个文件夹 【第一步】在A列…

VirtualBox Ubuntu Sever配置双网卡

Ubuntu 版本&#xff1a;Ubuntu Server 2404 vitrualBox 网卡配置&#xff1a; 如上配置后&#xff0c;ifconfig 只能看到 网卡1 应用了。要应用 网卡2 需要更改文件 /etc/netplan/50-cloud-init.yaml&#xff08;不同的ubuntu版本这个文件名可能不同&#xff09; 首先 ifcon…

如何在Linux上删除Systemd服务

Systemd是Linux 操作系统的系统和服务管理器&#xff0c;提供控制系统启动时启动哪些服务的标准流程。 有时&#xff0c;您可能出于各种原因需要删除systemd服务&#xff0c;例如不再需要、与其他服务冲突&#xff0c;或者您只是想清理系统。 Systemd使用单元文件来管理服务&…

OBD诊断(ISO15031) 04服务

文章目录 功能简介ISO 9141-2、ISO 14230-4和SAE J1850的诊断服务定义1、清除/重置与排放相关的诊断信息请求消息定义2、请求与排放相关的DTC响应消息定义3、报文示例 ISO 15765-4的诊断服务定义1、请求与排放相关的DTC请求消息定义2、请求与排放相关的DTC响应消息定义3、否定响…

深入详解RocketMQ源码安装与调试

1.源码下载 http://rocketmq.apache.org/dowloading/releases/ 2. 环境要求 64位系统JDK1.8(64位)Maven 3.2.x