ES 基本使用与二次封装

概述

基本了解

Elasticsearch 是一个开源的分布式搜索和分析引擎,基于 Apache Lucene 构建。它提供了对海量数据的快速全文搜索、结构化搜索和分析功能,是目前流行的大数据处理工具之一。主要特点即高效搜索、分布式存储、拓展性强

核心功能

  • 全文搜索: 提供对文本数据的快速匹配和排名能力
  • 实时数据处理: 支持实时写入、更新、删除和搜索
  • 分布式存储: 能够将数据分片(shard)存储在多个节点中,具有高可用性和容错性
  • 聚合分析: 支持对大数据进行复杂的统计分析,如平均值、最大值、分组统计等

核心概念

索引

索引是数据的逻辑组织单位,可以类比为数据库中的“数据库”。每个索引由多个文档组成,类似于一本书的目录

索引是查询的入口点,比如当你要查“小说类书籍”,会直接到“小说书架”查找,而不是其他书架

文档

文档是数据存储的基本单元,相当于关系型数据库中的“行”。每个文档是以 JSON 格式存储的键值对集合(这一点注意和MySQL进行区分)

每本书可以看作一个文档。每本书有具体的属性,比如书名、作者、出版年份、ISBN 号等

{
  "title": "The Catcher in the Rye",
  "author": "J.D. Salinger",
  "year": 1951,
  "genre": "Fiction"
}

字段

文档中的属性或键值对,比如书的“标题”、“作者”、“出版年份”等;可以简单理解为一本书的详细信息

注意:字段的类型可以指定(例如 textkeywordinteger 等),并决定了如何处理这些数据。例如,“标题”是 text 类型,支持全文搜索,而“ISBN” 是 keyword 类型,只支持精确匹配

分片

分片是数据的物理存储单位。每个索引可以分成多个分片,分布在不同的节点上以提升性能

类似哈利波特系列的书很多,在图书馆中分别放在一楼和二楼,当寻找的时候同时派人去一楼和二楼寻找,这样就可以节省寻找的时间;这也就对应着分片的主要的作用:分片使得查询和存储可以并行处理,提高系统性能;分片还提供了冗余和容错能力(通过副本)

节点

节点是 Elasticsearch 集群中的一个实例。每个节点都有特定的角色,比如主节点、数据节点

例如在图书馆中,可以将每个楼层看作一个节点,可以存储数据,也可以帮助查询;比如,楼层 1 存储小说,楼层 2 存储科技书籍,但两层楼之间可以互相配合

不同的节点可以扮演不同角色

  • 主节点(Master Node):负责管理整个图书馆的目录(分片的分布情况)
  • 数据节点(Data Node):实际存储书籍和处理搜索请求
  • 协调节点(Coordinator Node):负责分发和合并查询结果

集群

集群是一组相互协作的节点,共同存储和处理数据

整个图书馆可以看作是一个 Elasticsearch 集群,包含多个楼层(节点),书架(索引)被分布在各个楼层(节点)上

  • 集群中的节点可以随时增加或减少,确保高扩展性
  • 当有大批人需要搜索数据时,集群可以通过多个节点的并发处理快速完成任务

ES高性能的原因

倒排索引

倒排索引是 Elasticsearch 搜索速度快的核心技术,它记录的是每个单词在文档中的位置,而不是逐个文档搜索所有内容。

例如有三个文档,记录的内容分别是:文档1: "猫喜欢鱼";文档2: "狗喜欢骨头";文档3: "鱼喜欢水",那么倒排索引就会建立如下索引

词语        文档ID
喜欢        1, 2, 3
猫          1
狗          2
鱼          1, 3
骨头        2
水          3

当查找喜欢这个单词的时候,Elasticsearch就不会扫描全部的文档,而是直接从倒排索引中找到包含该单词的文档列表即可

综上所述:ES使用倒排索引避免了逐个扫描文档,直接定位到包含目标关键词的文档,查询时间随文档总量的增长几乎不变。

分布式架构

Elasticsearch 将数据分成多个 分片 存储在不同的节点上,查询时,会并行搜索所有分片,最后合并结果返回。

场景还假设在上面文档的内容中,将索引分为3个分片中,分布在三个节点上

  • 分片1: 包含文档 1~100
  • 分片2: 包含文档 101~200
  • 分片3: 包含文档 201~300

当用户查询“鱼”,Elasticsearch 会同时向 3 个分片发出请求:

  • 分片1 返回包含“鱼”的文档 1
  • 分片2 无结果
  • 分片3 返回包含“鱼”的文档 3

最后将结果合并返回给用户;就像超时收银的,不会将所有的客户都在一个收银节点,通过设置多个收银节点完成最后的收银工作

缓存机制

Elasticsearch 使用内存缓存和文件系统缓存来存储常用的查询结果,如果相同的查询被多次请求,Elasticsearch 会直接从缓存中返回结果,而无需重新计算

查询优化和分析器

lasticsearch 会对查询请求进行优化,比如避免不必要的计算、合并多个相同的查询条件等

分析器(Analyzer)是对文本数据的处理器,通常会对字段的内容进行分词、去停用词(如 "the"、"is")、小写化等操作;借助分词器和索引机制让全文搜索更加精准和迅速

使用

添加与访问测试

 创建索引库

POST /user/_doc
{
  "settings": {
    "analysis": {
      "analyzer": {
        "ik": {
          "tokenizer": "ik_max_word"
        }
      }
    }
  },
  "mappings": {
    "dynamic": true,
    "properties": {
      "nickname": {
        "type": "text",
        "analyzer": "ik_max_word"
      },
      "user_id": {
        "type": "keyword",
        "analyzer": "standard"
      },
      "phone": {
        "type": "keyword",
        "analyzer": "standard"
      },
      "description": {
        "type": "text",
        "enabled": false
      },
      "avatar_id": {
        "type": "keyword",
        "enabled": false
      }
    }
  }
}

新增数据

POST /user/_doc/_bulk
{ "index": { "_id": "1" } }
{ "user_id": "USER4b862aaa-2df8654a-7eb4bb65-e3507f66", "nickname": "昵称1", "phone": "手机号1", "description": "签名1", "avatar_id": "头像1" }
{ "index": { "_id": "2" } }
{ "user_id": "USER14eeea5-442771b9-0262e455-e46631d1", "nickname": "昵称2", "phone": "手机号2", "description": "签名2", "avatar_id": "头像2" }
{ "index": { "_id": "3" } }
{ "user_id": "USER484a6734-03a124f0-996c169d-d05c1869", "nickname": "昵称3", "phone": "手机号3", "description": "签名3", "avatar_id": "头像3" }
{ "index": { "_id": "4" } }
{ "user_id": "USER186ade83-4460d4a6-8c08068f-83127b5d", "nickname": "昵称4", "phone": "手机号4", "description": "签名4", "avatar_id": "头像4" }
{ "index": { "_id": "5" } }
{ "user_id": "USER6f19d074-c33891cf-23bf5a83-57189c19", "nickname": "昵称5", "phone": "手机号5", "description": "签名5", "avatar_id": "头像5" }
{ "index": { "_id": "6" } }
{ "user_id": "USER97605c64-9833ebb7-d0455353-35a59195", "nickname": "昵称6", "phone": "手机号6", "description": "签名6", "avatar_id": "头像6" }

 搜索所有数据

ES客户端接口

参数说明

  • indexName:指定 Elasticsearch 的索引名称
  • docType::指定文档的类型(在 Elasticsearch 7.x+ 中已被弃用)
  • id:文档的唯一标识符,用于获取、索引或删除
  • body:Elasticsearch 的请求体,通常为 JSON 格式
  • routing:路由参数,如果为空,则使用默认的路由规则

常用接口功能

搜索

  • 在 Elasticsearch 集群中搜索指定的索引,直到成功为止
  • 如果所有节点都未响应,则抛出 ConnectionException
cpr::Response search(
    const std::string &indexName,
    const std::string &docType,
    const std::string &body,
    const std::string &routing = std::string());

获取文档

  • 从集群中获取指定 ID 的文档
  • 如果所有节点都未响应,则抛出 ConnectionException
cpr::Response get(
    const std::string &indexName,
    const std::string &docType,
    const std::string &id = std::string(),
    const std::string &routing = std::string());

索引文档

  • 在集群中新增或更新文档
  • 如果 ID 未提供,Elasticsearch 将自动生成 ID
  • 如果所有节点都未响应,则抛出 ConnectionException
cpr::Response index(
    const std::string &indexName,
    const std::string &docType,
    const std::string &id,
    const std::string &body,
    const std::string &routing = std::string());

删除文档

  • 从集群中删除指定 ID 的文档
  • 如果所有节点都未响应,则抛出 ConnectionException
cpr::Response remove(
    const std::string &indexName,
    const std::string &docType,
    const std::string &id,
    const std::string &routing = std::string());

基本操作

预处理

创建索引然后插入数据 

搜索 

 通过客户端对指定内容进行搜索

二次封装

封装思路

索引创建

  • 动态设置索引名称、索引类型
  • 添加字段、设置字段类型及分词器设置
  • 构造的核心逻辑则通过Json::Value构造对应的请求正文

数据新增

  • 特定索引中插入文档
  • 文档格式以JSON构造,灵活支持动态字段和值

数据查询

  • 封装搜索语法 ,生成符合ES查询语法的JSON格式请求体
  • 支持复杂的查询条件,例如多条件组合 

数据删除

  • 封装删除请求接口,可以按索引或文档 ID 进行删除

具体实现

工具函数

Serialize:JSON数据序列化,将Value转换为字符串

    // 序列化
    bool Serialize(const Json::Value &val , std::string &dst)
    {
        Json::StreamWriterBuilder swb;
        swb.settings_["emitUTF8"] = true;
        std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
        std::stringstream ss;
        int ret = sw->write(val, &ss);
        if (ret != 0) {
            std::cout << "Json反序列化失败!\n";
            return false;
        }
        dst = ss.str();
        return true;
    }

UnSerialize:JSON数据反序列化,将字符串转换为Value

    // 反序列化
    bool UnSerialize(const std::string &src, Json::Value &val)
    {
        Json::CharReaderBuilder crb;
        std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
        std::string err;
        bool ret = cr->parse(src.c_str(), src.c_str() + src.size(), &val, &err);
        if (ret == false) {
            std::cout << "json反序列化失败: " << err << std::endl;
            return false;
        }
        return true;
    }   

创建索引

append

  • 作用:给索引字段添加映射配置(字段名称,字段类型,分析器名称,是否启用该字段)
  • 实现逻辑:创建字段的JSON配置,然后将其添加到_properties中,然后字段映射以键值对的形式进行存储
ESIndex& append(const std::string &key, 
            const std::string &type = "text", 
            const std::string &analyzer = "ik_max_word", 
            bool enabled = true) 
        {
            Json::Value fields;
            fields["type"] = type;
            fields["analyzer"] = analyzer;
            if (enabled == false ) fields["enabled"] = enabled;
            _properties[key] = fields;
            return *this;
        }

create

  • 作用:根据settings和mappings配置,通过客户端发送HTTP请求创建Elasticsearch索引
  • 逻辑
    • 设置索引的mappins配置
    • 序列化索引配置为JSON字符串
    • 通过_client调用Elasticsearch的接口创建索引
bool create(const std::string &index_id = "default_index_id") {
            Json::Value mappings;
            mappings["dynamic"] = true;
            mappings["properties"] = _properties;
            _index["mappings"] = mappings;

            std::string body;
            bool ret = Serialize(_index, body);
            if (ret == false) {
                LOG_ERROR("索引序列化失败!");
                return false;
            }
            LOG_DEBUG("{}", body);
            //2. 发起搜索请求
            try {
                auto rsp = _client->index(_name, _type, index_id, body);
                if (rsp.status_code < 200 || rsp.status_code >= 300) {
                    LOG_ERROR("创建ES索引 {} 失败,响应状态码异常: {}", _name, rsp.status_code);
                    return false;
                }
            } catch(std::exception &e) {
                LOG_ERROR("创建ES索引 {} 失败: {}", _name, e.what());
                return false;
            }
            return true;
        }

新增数据

append

  • 作用:添加一条记录到_item中
  • 逻辑:将传入的键值对添加到_item对象中
template<typename T>
        ESInsert &append(const std::string &key, const T &val){
            _item[key] = val;
            return *this;
        }
        

insert

  • 作用:将_item序列化为JSON字符串
  • 逻辑
    • 通过序列化函数将_item转换为JSON格式的字符串body
    • 发起请求,将JSON数据发送到Elasticsearch中,然后检查是否发送成功
 bool insert(const std::string id = "") {
            std::string body;
            bool ret = Serialize(_item, body);
            if (ret == false) {
                LOG_ERROR("索引序列化失败!");
                return false;
            }
            LOG_DEBUG("{}", body);
            //2. 发起搜索请求
            try {
                auto rsp = _client->index(_name, _type, id, body);
                if (rsp.status_code < 200 || rsp.status_code >= 300) {
                    LOG_ERROR("新增数据 {} 失败,响应状态码异常: {}", body, rsp.status_code);
                    return false;
                }
            } catch(std::exception &e) {
                LOG_ERROR("新增数据 {} 失败: {}", body, e.what());
                return false;
            }
            return true;
        }

删除数据

remove

  • 作用:删除指定ID的文档
  • 逻辑:
    • 调用客户端的remove方法,向Elasticsearch发送删除请求
    • 判断是否成功,同时对错误进行捕捉,防止被其他错误中断
class ESRemove {
    public:
        ESRemove(std::shared_ptr<elasticlient::Client> &client, 
            const std::string &name, 
            const std::string &type = "_doc"):
            _name(name), _type(type), _client(client){}
        bool remove(const std::string &id) {
            try {
                auto rsp = _client->remove(_name, _type, id);
                if (rsp.status_code < 200 || rsp.status_code >= 300) {
                    LOG_ERROR("删除数据 {} 失败,响应状态码异常: {}", id, rsp.status_code);
                    return false;
                }
            } catch(std::exception &e) {
                LOG_ERROR("删除数据 {} 失败: {}", id, e.what());
                return false;
            }
            return true;
        }
    private:
        std::string _name;
        std::string _type;
        std::shared_ptr<elasticlient::Client> _client;
};

数据查询

  • 主要功能
    • 提供接口构建多条件的Elasticsearch布尔查询
    • 将查询条件序列化为JSON格式的请求体
    • 调用Elasticsearch客户端执行查询
    • 解析查询结果并返回文档数据
class ESSearch {
    public:
        ESSearch(std::shared_ptr<elasticlient::Client> &client, 
            const std::string &name, 
            const std::string &type = "_doc"):
            _name(name), _type(type), _client(client){}
        ESSearch& append_must_not_terms(const std::string &key, const std::vector<std::string> &vals) {
            Json::Value fields;
            for (const auto& val : vals){
                fields[key].append(val);
            }
            Json::Value terms;
            terms["terms"] = fields;
            _must_not.append(terms);
            return *this;
        }
        ESSearch& append_should_match(const std::string &key, const std::string &val) {
            Json::Value field;
            field[key] = val;
            Json::Value match;
            match["match"] = field;
            _should.append(match);
            return *this;
        }
        ESSearch& append_must_term(const std::string &key, const std::string &val) {
            Json::Value field;
            field[key] = val;
            Json::Value term;
            term["term"] = field;
            _must.append(term);
            return *this;
        }
        ESSearch& append_must_match(const std::string &key, const std::string &val){
            Json::Value field;
            field[key] = val;
            Json::Value match;
            match["match"] = field;
            _must.append(match);
            return *this;
        }
        Json::Value search(){
            Json::Value cond;
            if (_must_not.empty() == false) cond["must_not"] = _must_not;
            if (_should.empty() == false) cond["should"] = _should;
            if (_must.empty() == false) cond["must"] = _must;
            Json::Value query;
            query["bool"] = cond;
            Json::Value root;
            root["query"] = query;

            std::string body;
            bool ret = Serialize(root, body);
            if (ret == false) {
                LOG_ERROR("索引序列化失败!");
                return Json::Value();
            }
            LOG_DEBUG("{}", body);
            //2. 发起搜索请求
            cpr::Response rsp;
            try {
                rsp = _client->search(_name, _type, body);
                if (rsp.status_code < 200 || rsp.status_code >= 300) {
                    LOG_ERROR("检索数据 {} 失败,响应状态码异常: {}", body, rsp.status_code);
                    return Json::Value();
                }
            } catch(std::exception &e) {
                LOG_ERROR("检索数据 {} 失败: {}", body, e.what());
                return Json::Value();
            }
            //3. 需要对响应正文进行反序列化
            LOG_DEBUG("检索响应正文: [{}]", rsp.text);
            Json::Value json_res;
            ret = UnSerialize(rsp.text, json_res);
            if (ret == false) {
                LOG_ERROR("检索数据 {} 结果反序列化失败", rsp.text);
                return Json::Value();
            }
            return json_res["hits"]["hits"];
        }
    private:
        std::string _name;
        std::string _type;
        Json::Value _must_not;
        Json::Value _should;
        Json::Value _must;
        std::shared_ptr<elasticlient::Client> _client;
};
#pragma once
#include <elasticlient/client.h>
#include <cpr/cpr.h>
#include <json/json.h>
#include <iostream>
#include <memory>
#include "logger.hpp"

namespace mag {

    
    /// 工具函数
    

    /**
     * @brief 将 Json::Value 数据序列化为 JSON 字符串
     * @param val 要序列化的 Json::Value 对象
     * @param dst 序列化后的 JSON 字符串
     * @return true 序列化成功
     * @return false 序列化失败
     */
    bool Serialize(const Json::Value &val, std::string &dst) {
        Json::StreamWriterBuilder swb;
        swb.settings_["emitUTF8"] = true; // 输出为 UTF-8 编码
        std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
        std::stringstream ss;
        int ret = sw->write(val, &ss); // 执行序列化
        if (ret != 0) {
            std::cout << "Json序列化失败!\n";
            return false;
        }
        dst = ss.str(); // 将结果写入目标字符串
        return true;
    }

    /**
     * @brief 将 JSON 字符串反序列化为 Json::Value 对象
     * @param src 待解析的 JSON 字符串
     * @param val 解析后的 Json::Value 对象
     * @return true 反序列化成功
     * @return false 反序列化失败
     */
    bool UnSerialize(const std::string &src, Json::Value &val) {
        Json::CharReaderBuilder crb;
        std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
        std::string err;
        bool ret = cr->parse(src.c_str(), src.c_str() + src.size(), &val, &err); // 执行解析
        if (!ret) {
            std::cout << "Json反序列化失败: " << err << std::endl;
            return false;
        }
        return true;
    }

    
    /// 索引创建模块
    

    /**
     * @brief 索引创建类:用于定义和创建 Elasticsearch 索引
     */
    class ESIndex {
    public:
        /**
         * @brief 构造函数:初始化索引创建模块
         * @param client Elasticsearch 客户端实例
         * @param name 索引名称
         * @param type 文档类型(默认 "_doc")
         */
        ESIndex(std::shared_ptr<elasticlient::Client> &client, 
                const std::string &name, 
                const std::string &type = "_doc"):
            _name(name), _type(type), _client(client) {
            Json::Value analysis;
            Json::Value analyzer;
            Json::Value ik;
            Json::Value tokenizer;
            tokenizer["tokenizer"] = "ik_max_word"; // 设置分词器为 ik_max_word
            ik["ik"] = tokenizer;
            analyzer["analyzer"] = ik;
            analysis["analysis"] = analyzer;
            _index["settings"] = analysis; // 设置索引的 settings
        }

        /**
         * @brief 添加字段映射
         * @param key 字段名称
         * @param type 字段类型(默认 "text")
         * @param analyzer 分词器名称(默认 "ik_max_word")
         * @param enabled 是否启用字段(默认 true)
         * @return ESIndex& 支持链式调用
         */
        ESIndex& append(const std::string &key, 
                        const std::string &type = "text", 
                        const std::string &analyzer = "ik_max_word", 
                        bool enabled = true) {
            Json::Value fields;
            fields["type"] = type;       // 设置字段类型
            fields["analyzer"] = analyzer; // 设置字段分词器
            if (!enabled) fields["enabled"] = enabled; // 如果禁用字段
            _properties[key] = fields;  // 添加字段到映射配置中
            return *this;
        }

        /**
         * @brief 创建索引
         * @param index_id 索引 ID(默认 "default_index_id")
         * @return true 创建成功
         * @return false 创建失败
         */
        bool create(const std::string &index_id = "default_index_id") {
            Json::Value mappings;
            mappings["dynamic"] = true;          // 设置动态映射
            mappings["properties"] = _properties; // 添加字段映射
            _index["mappings"] = mappings;

            std::string body;
            bool ret = Serialize(_index, body); // 将索引配置序列化为 JSON
            if (!ret) {
                LOG_ERROR("索引序列化失败!");
                return false;
            }
            LOG_DEBUG("{}", body);

            try {
                auto rsp = _client->index(_name, _type, index_id, body); // 调用客户端创建索引
                if (rsp.status_code < 200 || rsp.status_code >= 300) {
                    LOG_ERROR("创建ES索引 {} 失败,响应状态码异常: {}", _name, rsp.status_code);
                    return false;
                }
            } catch (std::exception &e) {
                LOG_ERROR("创建ES索引 {} 失败: {}", _name, e.what());
                return false;
            }
            return true;
        }

    private:
        std::string _name;                  // 索引名称
        std::string _type;                  // 文档类型
        Json::Value _properties;            // 字段映射配置
        Json::Value _index;                 // 索引完整配置
        std::shared_ptr<elasticlient::Client> _client; // 客户端实例
    };

    
    /// 索引插入模块
    

    /**
     * @brief 索引插入类:支持动态数据插入到 Elasticsearch 索引
     */
    class ESInsert {
    public:
        /**
         * @brief 构造函数:初始化插入模块
         * @param client Elasticsearch 客户端实例
         * @param name 索引名称
         * @param type 文档类型(默认 "_doc")
         */
        ESInsert(std::shared_ptr<elasticlient::Client> &client, 
                 const std::string &name, 
                 const std::string &type = "_doc"):
            _name(name), _type(type), _client(client) {}

        /**
         * @brief 添加字段和数据
         * @tparam T 数据类型
         * @param key 字段名称
         * @param val 字段值
         * @return ESInsert& 支持链式调用
         */
        template<typename T>
        ESInsert& append(const std::string &key, const T &val) {
            _item[key] = val; // 动态添加字段
            return *this;
        }

        /**
         * @brief 插入数据到 Elasticsearch
         * @param id 文档 ID(默认为自动生成)
         * @return true 插入成功
         * @return false 插入失败
         */
        bool insert(const std::string id = "") {
            std::string body;
            bool ret = Serialize(_item, body); // 将数据序列化为 JSON
            if (!ret) {
                LOG_ERROR("数据序列化失败!");
                return false;
            }
            LOG_DEBUG("{}", body);

            try {
                auto rsp = _client->index(_name, _type, id, body); // 调用客户端插入数据
                if (rsp.status_code < 200 || rsp.status_code >= 300) {
                    LOG_ERROR("插入数据 {} 失败,响应状态码异常: {}", body, rsp.status_code);
                    return false;
                }
            } catch (std::exception &e) {
                LOG_ERROR("插入数据 {} 失败: {}", body, e.what());
                return false;
            }
            return true;
        }

    private:
        std::string _name;                  // 索引名称
        std::string _type;                  // 文档类型
        Json::Value _item;                  // 待插入的数据
        std::shared_ptr<elasticlient::Client> _client; // 客户端实例
    };

    
    /// 数据删除模块
    

    /**
     * @brief 删除模块:删除 Elasticsearch 中的文档
     */
    class ESRemove {
    public:
        /**
         * @brief 构造函数:初始化删除模块
         * @param client Elasticsearch 客户端实例
         * @param name 索引名称
         * @param type 文档类型(默认 "_doc")
         */
        ESRemove(std::shared_ptr<elasticlient::Client> &client, 
                 const std::string &name, 
                 const std::string &type = "_doc"):
            _name(name), _type(type), _client(client) {}

        /**
         * @brief 删除指定文档
         * @param id 文档 ID
         * @return true 删除成功
         * @return false 删除失败
         */
        bool remove(const std::string &id) {
            try {
                auto rsp = _client->remove(_name, _type, id); // 调用客户端删除文档
                if (rsp.status_code < 200 || rsp.status_code >= 300) {
                    LOG_ERROR("删除数据 {} 失败,响应状态码异常: {}", id, rsp.status_code);
                    return false;
                }
            } catch (std::exception &e) {
                LOG_ERROR("删除数据 {} 失败: {}", id, e.what());
                return false;
            }
            return true;
        }

    private:
        std::string _name;                  // 索引名称
        std::string _type;                  // 文档类型
        std::shared_ptr<elasticlient::Client> _client; // 客户端实例
    };

    
    /// 数据查找模块
    

    /**
     * @brief 数据查找模块:用于执行复杂查询
     */
    class ESSearch {
    public:
        /**
         * @brief 构造函数:初始化查询模块
         * @param client Elasticsearch 客户端实例
         * @param name 索引名称
         * @param type 文档类型(默认 "_doc")
         */
        ESSearch(std::shared_ptr<elasticlient::Client> &client, 
                 const std::string &name, 
                 const std::string &type = "_doc"):
            _name(name), _type(type), _client(client) {}

        /**
         * @brief 添加 must_not 条件
         * @param key 字段名称
         * @param vals 排除的值列表
         * @return ESSearch& 支持链式调用
         */
        ESSearch& append_must_not_terms(const std::string &key, const std::vector<std::string> &vals) {
            Json::Value fields;
            for (const auto &val : vals) {
                fields[key].append(val);
            }
            Json::Value terms;
            terms["terms"] = fields;
            _must_not.append(terms); // 添加到 must_not 条件
            return *this;
        }

        /**
         * @brief 添加 should 条件
         * @param key 字段名称
         * @param val 匹配的值
         * @return ESSearch& 支持链式调用
         */
        ESSearch& append_should_match(const std::string &key, const std::string &val) {
            Json::Value field;
            field[key] = val;
            Json::Value match;
            match["match"] = field;
            _should.append(match); // 添加到 should 条件
            return *this;
        }

        /**
         * @brief 添加 must 条件(精确匹配)
         * @param key 字段名称
         * @param val 精确匹配的值
         * @return ESSearch& 支持链式调用
         */
        ESSearch& append_must_term(const std::string &key, const std::string &val) {
            Json::Value field;
            field[key] = val;
            Json::Value term;
            term["term"] = field;
            _must.append(term); // 添加到 must 条件
            return *this;
        }

        /**
         * @brief 添加 must 条件(模糊匹配)
         * @param key 字段名称
         * @param val 模糊匹配的值
         * @return ESSearch& 支持链式调用
         */
        ESSearch& append_must_match(const std::string &key, const std::string &val) {
            Json::Value field;
            field[key] = val;
            Json::Value match;
            match["match"] = field;
            _must.append(match); // 添加到 must 条件
            return *this;
        }

        /**
         * @brief 执行查询
         * @return Json::Value 查询结果
         */
        Json::Value search() {
            Json::Value cond;
            if (!_must_not.empty()) cond["must_not"] = _must_not;
            if (!_should.empty()) cond["should"] = _should;
            if (!_must.empty()) cond["must"] = _must;

            Json::Value query;
            query["bool"] = cond;

            Json::Value root;
            root["query"] = query;

            std::string body;
            bool ret = Serialize(root, body); // 序列化查询条件
            if (!ret) {
                LOG_ERROR("查询条件序列化失败!");
                return Json::Value();
            }
            LOG_DEBUG("查询请求体: {}", body);

            cpr::Response rsp;
            try {
                rsp = _client->search(_name, _type, body); // 执行查询
                if (rsp.status_code < 200 || rsp.status_code >= 300) {
                    LOG_ERROR("查询失败,响应状态码异常: {}", rsp.status_code);
                    return Json::Value();
                }
            } catch (std::exception &e) {
                LOG_ERROR("查询失败: {}", e.what());
                return Json::Value();
            }

            LOG_DEBUG("查询响应: {}", rsp.text);

            Json::Value json_res;
            ret = UnSerialize(rsp.text, json_res); // 解析响应结果
            if (!ret) {
                LOG_ERROR("查询结果反序列化失败!");
                return Json::Value();
            }
            return json_res["hits"]["hits"]; // 返回查询结果
        }

    private:
        std::string _name;                  // 索引名称
        std::string _type;                  // 文档类型
        Json::Value _must_not;              // must_not 条件
        Json::Value _should;                // should 条件
        Json::Value _must;                  // must 条件
        std::shared_ptr<elasticlient::Client> _client; // 客户端实例
    };
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java项目实战II基于SPringBoot的玩具销售商城管理系统(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、核心代码 五、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 随着儿童娱乐与教育需求的…

Python安装出现严重错误的解决方法_0x80070643-安装时发生严重错误

使用这个软件MicrosoftProgram_Install_and_Uninstall.meta.diagcab把关于Python一个个组件全部删除&#xff0c;然后就能够重新安装Python了 修复阻止程序安装或删除的问题 - Microsoft 支持 这里下载

Java语言编程,通过阿里云mongo数据库监控实现数据库的连接池优化

一、背景 线上程序连接mongos超时&#xff0c;mongo监控显示连接数已使用100%。 java程序报错信息&#xff1a; org.mongodb.driver.connection: Closed connection [connectionId{localValue:1480}] to 192.168.10.16:3717 because there was a socket exception raised by…

微信小程序全局配置:导航栏、下拉刷新与上拉触底设置教程

微信小程序全局配置:导航栏、下拉刷新与上拉触底设置教程 引言 微信小程序作为一种新兴的轻量级应用,凭借其便捷性和丰富的功能受到了广泛的欢迎。在开发小程序的过程中,合理配置全局属性是提升用户体验的关键。本文将深入探讨小程序的全局配置中的window选项,重点介绍导…

YOLOv11融合[ECCV 2018]RCAN中的RCAB模块及相关改进思路

YOLOv11v10v8使用教程&#xff1a; YOLOv11入门到入土使用教程 YOLOv11改进汇总贴&#xff1a;YOLOv11及自研模型更新汇总 《Image Super-Resolution Using Very Deep Residual Channel Attention Networks》 一、 模块介绍 论文链接&#xff1a;https://arxiv.org/abs/1807…

linux ubuntu的脚本知

目录 一、变量的引用 二、判断指定的文件是否存在 三、判断目录是否存在 四、判断最近一次命令执行是否成功 五、一些比较符号 六、"文件"的读取和写入 七、echo打印输出 八、ubuntu切换到root用户 N、其它可以参考的网址 脚本功能强大&#xff0c;用起来也…

前端:JavaScript (学习笔记)【2】

目录 一&#xff0c;数组的使用 1&#xff0c;数组的创建 [ ] 2&#xff0c;数组的元素和长度 3&#xff0c;数组的遍历方式 4&#xff0c;数组的常用方法 二&#xff0c;JavaScript中的对象 1&#xff0c;常用对象 &#xff08;1&#xff09;String和java中的Stri…

【Git】工作区、暂存区和版本库

目录 一、基本概念&#xff1a; 关系图&#xff1a; 1. 工作区&#xff08;Working Directory&#xff09; $ 1.1 工作区功能 $ 1.2 工作区特点 2. 暂存区&#xff08;Staging Area&#xff09; $ 2.1 暂存区功能 $ 2.2 暂存区特点 $ 2.3 常用命令 3. 版本库&#xff08…

【Linux | 计网】TCP协议详解:从定义到连接管理机制

目录 1.TCP协议的定义&#xff1a; 2.TCP 协议段格式 3.TCP两种通信方式 4.确认应答(ACK)机制 解决“后发先至”问题 5.超时重传机制 那么, 超时的时间如何确定? 6.连接管理机制&#xff1a; 6.1.三次握手&#xff1a; 为什么需要3次握手&#xff0c;一次两次不行吗…

Springboot系列之:创建Springboot项目,Springboot整合MyBatis-plus

Springboot系列之&#xff1a;创建Springboot项目&#xff0c;Springboot整合MyBatis-plus 一、快速创建Spring boot项目二、项目完整目录三、pom.xml四、application.yaml五、实体类六、mapper七、IService接口八、Service实现类九、配置类十、枚举十一、增删改查测试类十二、…

java基础面试题笔记(基础篇)

网上始终找不到令自己满意的面试题&#xff0c;所以我打算自己整理面试题&#xff0c;从简单的到难的&#xff0c;尽量简单准确描述回答降低大家理解和背的难度&#xff0c;有错误或者有更好的回答请在评论回复我&#xff0c;感谢大家。 什么是java&#xff1f; 回答&#xff…

编译 LLVM 源码,使用 Clion 调试 clang

版权归作者所有&#xff0c;如有转发&#xff0c;请注明文章出处&#xff1a;https://cyrus-studio.github.io/blog/ 1. LLVM 简介 LLVM 是一个开源的编译器基础架构&#xff0c;最初由 Chris Lattner 于 2000 年在伊利诺伊大学开发&#xff0c;后来成为一个广泛应用于编译器和…

[代码随想录打卡Day22] 理论基础 77. 组合 216.组合总和III 17.电话号码的字母组合

理论基础 有递归就有回溯。回溯搜索是一种纯暴力搜索算法。我们一层一层递归到最底层收获结果&#xff0c;比如下面我们最后一层1操作之后&#xff0c;我们只有撤销这个操作回退到上一个节点才能遍历该层的其他节点&#xff0c;这个回退撤销操作就是回溯。 回溯法&#xff0…

大模型工程化部署:使用FastChat部署基于OpenAI API兼容大模型服务

FastChat是加州大学伯克利分校LM-SYS发布的一个用于训练、服务和评估基于大型语言模型的聊天机器人的开放平台。 项目地址&#xff1a;https://github.com/lm-sys/FastChat.git 其核心功能包括&#xff1a; 最先进 LLM 模型的权重、训练代码和评估代码。 带有 WebUI 和与 Op…

102.【C语言】数据结构之用堆对数组排序

0.前置知识 向上调整: 向下调整: 1.对一个无序的数组排升序和降序 排升序问题 建大根堆还是小根堆? 错误想法 由小根堆的定义:树中所有的父节点的值都小于或等于孩子节点的值,这样排出来的数组时升序的,建小根堆调用向上调整函数即可(把画圈的地方改成<即可) arr未…

彻底理解微服务的作用和解决方案

一.微服务有什么好处&#xff1f; 微服务优点很多&#xff0c;但是我们通常说一个东西好肯定会跟另一个东西比较&#xff0c;通常说微服务好会和单体项目进行比较&#xff0c;通常情况下微服务都是从单体项目拆分而来的&#xff0c;但是对于有些大型公司&#xff0c;不差钱&…

Harbor安装、HTTPS配置、修改端口后不可访问?

Harbor安装、HTTPS配置、修改端口后不可访问&#xff1f; 大家好&#xff0c;我是秋意零。今天分享Harbor相关内容&#xff0c;安装部分可完全参考官方文档&#xff0c;写的也比较详细。 安装Harbor 官方文档&#xff1a;https://goharbor.io/docs/2.12.0/install-config/ …

表单校验规则

这里简单记录下vue使用表单时候&#xff0c;给表单添加校验规则&#xff0c;直接上代码 <script setup>import { ref } from vue// 定义表单对象const form ref({account: ,password: ,agree: true})// 定义表单验证规则const rules {account: [{required: true, mess…

spf算法、三类LSA、区间防环路机制/规则、虚连接

1.构建spf树&#xff1a; 路由器将自己作为最短路经树的树根根据Router-LSA和Network-LSA中的拓扑信息,依次将Cost值最小的路由器添加到SPF树中。路由器以Router ID或者DR标识。广播网络中DR和其所连接路由器的Cost值为0。SPF树中只有单向的最短路径,保证了OSPF区域内路由计管不…

电子电气架构 -- ASIL D安全实现策略

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 所有人的看法和评价都是暂时的&#xff0c;只有自己的经历是伴随一生的&#xff0c;几乎所有的担忧和畏惧…