Node.js 操作 ElasticSearch 完整指南:从安装到实战

本文将手把手教你如何搭建 ElasticSearch 环境,并通过 Node.js 实现高效数据检索。包含 10+ 个可直接复用的代码片段,助你快速掌握搜索、聚合等核心功能!

环境搭建篇

1. ElasticSearch 安装要点

下载

es下载连接

下载下来后,进入 bin 目录,终端运行第一个文件,即可启动es。

修改密码

进入 bin 目录下,终端输入:

bash 复制代码
 .\elasticsearch-reset-password -u elastic -i

输入两次密码即可修改超级用户 elastic 的密码。

然后访问 http://localhost:9200 。输入密码和账号后,若返回以下信息则代表修改密码成功:

json 复制代码
{
  "name" : "Win10-2024UVSXG",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "oan-H91LSSiReCuNSDWKIA",
  "version" : {
    "number" : "8.15.0",
    "build_flavor" : "default",
    "build_type" : "zip",
    "build_hash" : "1a77947f34deddb41af25e6f0ddb8e830159c179",
    "build_date" : "2024-08-05T10:05:34.233336849Z",
    "build_snapshot" : false,
    "lucene_version" : "9.11.1",
    "minimum_wire_compatibility_version" : "7.17.0",
    "minimum_index_compatibility_version" : "7.0.0"
  },
  "tagline" : "You Know, for Search"
}

2. Kibana 联动配置

下载

Kibana 下载链接

注意: 下载的 kibana 的版本要与 es 一致,否则可能会报错,无法访问 Kibana

修改Kibana配置文件

需要进入到Kibana目录中,修改 /config/kibana.yml 文件。设置访问端口、ip、es账号密码。

注意: es的账号密码不能使用 elastic 超级用户,但是默认有一个 kibana_system 用户,只需在es中修改 kibana_system 用户密码即可。

启动

进入项目的 bin 目录中,打开终端运行第一个文件即可。

最后访问 http://localhost:5601 即可。

Node.js 核心操作篇

ElasticSearch 和 Kibana 的安装与使用指南

引言

ElasticSearch 是一个强大的开源搜索和分析引擎,而 Kibana 则是 ElasticSearch 的可视化工具。本文将详细介绍如何下载、安装和配置 ElasticSearch 和 Kibana,以及如何在 Node.js 中使用 ElasticSearch 进行数据操作。


ElasticSearch 部分

下载 ElasticSearch

  1. 访问 ElasticSearch 官方下载页面
  2. 选择适合您操作系统的版本进行下载。

启动 ElasticSearch

  1. 下载完成后,解压文件并进入 bin 目录。
  2. 在终端中运行第一个文件(Windows 用户运行 .bat 文件,Linux/macOS 用户运行 .sh 文件)。
  3. 启动成功后,ElasticSearch 默认运行在 http://localhost:9200

修改 ElasticSearch 密码

  1. 进入 bin 目录,在终端输入以下命令:

    bash 复制代码
    .\elasticsearch-reset-password -u elastic -i
  2. 按照提示输入两次新密码。

  3. 访问 http://localhost:9200,使用账号 elastic 和新密码登录。如果返回类似以下信息,则表示密码修改成功:

    json 复制代码
    {
      "name": "Win10-2024UVSXG",
      "cluster_name": "elasticsearch",
      "cluster_uuid": "oan-H91LSSiReCuNSDWKIA",
      "version": {
        "number": "8.15.0",
        "build_flavor": "default",
        "build_type": "zip",
        "build_hash": "1a77947f34deddb41af25e6f0ddb8e830159c179",
        "build_date": "2024-08-05T10:05:34.233336849Z",
        "build_snapshot": false,
        "lucene_version": "9.11.1",
        "minimum_wire_compatibility_version": "7.17.0",
        "minimum_index_compatibility_version": "7.0.0"
      },
      "tagline": "You Know, for Search"
    }

Kibana 部分

下载 Kibana

  1. 访问 Kibana 官方下载页面
  2. 注意: 下载的 Kibana 版本必须与 ElasticSearch 版本一致,否则可能会出现兼容性问题。

修改 Kibana 配置文件

  1. 进入 Kibana 目录,找到 /config/kibana.yml 文件。
  2. 修改以下配置项:
    • server.port: Kibana 的访问端口(默认为 5601)。
    • server.host: Kibana 的访问 IP(默认为 localhost)。
    • elasticsearch.usernameelasticsearch.password: 使用 kibana_system 用户的账号密码(需先在 ElasticSearch 中修改该用户的密码)。
  3. 保存配置文件。

启动 Kibana

  1. 进入 Kibana 的 bin 目录。
  2. 在终端中运行第一个文件(Windows 用户运行 .bat 文件,Linux/macOS 用户运行 .sh 文件)。
  3. 启动成功后,访问 http://localhost:5601 即可进入 Kibana 界面。

Node.js 中使用 ElasticSearch

安装依赖

在 Node.js 项目中安装 ElasticSearch 客户端库:

bash 复制代码
npm install @elastic/elasticsearch

基本使用

以下是一些常见的 ElasticSearch 操作示例:

1. 初始化客户端 (支持多种认证方式)
js 复制代码
const { Client } = require('@elastic/elasticsearch');
// 基础认证
const client = new Client({
  node: 'http://localhost:9200',
  auth: { username: 'elastic', password: 'yourpassword' }
});
// API Key 认证
const apiKeyClient = new Client({
  node: 'http://localhost:9200',
  auth: { apiKey: 'base64EncodedKey' }
});
// 云服务连接
const cloudClient = new Client({
  cloud: { id: 'my-cloud-id' },
  auth: { username: 'elastic', password: 'cloudpassword' }
});
2. 创建索引并添加数据
js 复制代码
const user = await client.index({
    index: 'user-data',
    document: {
        user: 1,
        age: 18,
        name: 'jack',
    }
});
3. 查询数据
js 复制代码
const response = await client.get({
    index: 'user-data',
    id: user._id // 可以指定 ID 或使用自动生成的 ID
});
4. 搜索数据
js 复制代码
const result = await client.search({
    index: 'user-data',
    query: {
        match: {
            name: 'jack' // 模糊查询
        }
    },
    size: 1 // 返回结果数量
});
console.log(result.hits.hits); // 打印搜索结果
5. 删除数据
js 复制代码
await client.delete({
    index: 'user-data',
    id: user._id
});
6. 搜索所有数据
js 复制代码
const response = await client.search({
    index: 'users',
    query: {
        match_all: {}, // 空对象表示匹配所有
    },
    size: 100 // 返回 100 条数据
});
7. 索引管理
创建索引(带映射)
javascript 复制代码
async function createIndexWithMapping() {
  try {
    const response = await client.indices.create({
      index: 'products',
      body: {
        mappings: {
          properties: {
            name: { type: 'text' },
            price: { type: 'float' },
            description: { type: 'text' },
            tags: { type: 'keyword' },
            created_at: { type: 'date' }
          }
        }
      }
    });
    console.log('索引创建成功:', response);
  } catch (error) {
    console.error('索引创建失败:', error.meta.body.error);
  }
}
检查索引是否存在
javascript 复制代码
async function checkIndexExists(indexName) {
  try {
    const exists = await client.indices.exists({ index: indexName });
    console.log(`索引 ${indexName} 存在:`, exists);
    return exists;
  } catch (error) {
    console.error('检查索引失败:', error);
    return false;
  }
}
删除索引
javascript 复制代码
async function deleteIndex(indexName) {
  try {
    const response = await client.indices.delete({ index: indexName });
    console.log('索引删除成功:', response);
    return response;
  } catch (error) {
    console.error('索引删除失败:', error.meta.body.error);
    throw error;
  }
}
8. 文档操作
批量插入文档
javascript 复制代码
async function bulkInsert() {
  const dataset = [
    { id: 1, name: 'iPhone 13', price: 799, category: 'phone' },
    { id: 2, name: 'MacBook Pro', price: 1299, category: 'laptop' },
    { id: 3, name: 'AirPods Pro', price: 249, category: 'accessory' }
  ];

  const body = dataset.flatMap(doc => [
    { index: { _index: 'products', _id: doc.id } },
    doc
  ]);

  try {
    const { body: bulkResponse } = await client.bulk({ body });
    
    if (bulkResponse.errors) {
      console.log('批量插入部分失败:', bulkResponse.items);
    } else {
      console.log('批量插入成功');
    }
  } catch (error) {
    console.error('批量插入失败:', error);
  }
}
更新文档
javascript 复制代码
async function updateDocument(index, id, updates) {
  try {
    const response = await client.update({
      index,
      id,
      body: {
        doc: updates
      }
    });
    console.log('文档更新成功:', response);
    return response;
  } catch (error) {
    console.error('文档更新失败:', error.meta.body.error);
    throw error;
  }
}

// 使用示例
// updateDocument('products', 1, { price: 849 });
部分更新与脚本更新
javascript 复制代码
async function updateWithScript() {
  try {
    const response = await client.update({
      index: 'products',
      id: 1,
      body: {
        script: {
          source: 'ctx._source.price += params.price_diff',
          lang: 'painless',
          params: {
            price_diff: 50
          }
        }
      }
    });
    console.log('脚本更新成功:', response);
  } catch (error) {
    console.error('脚本更新失败:', error);
  }
}
9. 高级搜索查询
多条件复合查询
javascript 复制代码
async function complexSearch() {
  try {
    const response = await client.search({
      index: 'products',
      body: {
        query: {
          bool: {
            must: [
              { match: { category: 'phone' } }
            ],
            filter: [
              { range: { price: { gte: 500, lte: 1000 } } }
            ],
            should: [
              { match: { name: 'pro' } }
            ],
            minimum_should_match: 1
          }
        },
        sort: [
          { price: { order: 'desc' } }
        ],
        highlight: {
          fields: {
            name: {},
            description: {}
          }
        }
      }
    });
    
    console.log('搜索结果:', response.hits.hits);
    return response.hits.hits;
  } catch (error) {
    console.error('搜索失败:', error);
    throw error;
  }
}
聚合查询
javascript 复制代码
async function aggregateSearch() {
  try {
    const response = await client.search({
      index: 'products',
      body: {
        size: 0,
        aggs: {
          categories: {
            terms: { field: 'category.keyword', size: 10 },
            aggs: {
              avg_price: { avg: { field: 'price' } },
              max_price: { max: { field: 'price' } }
            }
          },
          price_stats: {
            stats: { field: 'price' }
          }
        }
      }
    });
    
    console.log('分类聚合结果:', response.aggregations.categories.buckets);
    console.log('价格统计:', response.aggregations.price_stats);
    return response.aggregations;
  } catch (error) {
    console.error('聚合查询失败:', error);
    throw error;
  }
}
全文搜索与高亮
javascript 复制代码
async function fullTextSearch() {
  try {
    const response = await client.search({
      index: 'products',
      body: {
        query: {
          multi_match: {
            query: 'pro',
            fields: ['name^3', 'description'], // name字段权重更高
            type: 'best_fields'
          }
        },
        highlight: {
          pre_tags: ['<em>'],
          post_tags: ['</em>'],
          fields: {
            name: {},
            description: {}
          }
        }
      }
    });
    
    console.log('高亮搜索结果:');
    response.hits.hits.forEach(hit => {
      console.log(`ID: ${hit._id}, 分数: ${hit._score}`);
      console.log('高亮:', hit.highlight);
    });
  } catch (error) {
    console.error('全文搜索失败:', error);
  }
}
10. 实战案例:电商商品搜索
javascript 复制代码
class ProductSearch {
  constructor() {
    this.client = new Client({ node: 'http://localhost:9200' });
    this.indexName = 'ecommerce_products';
  }

  async initIndex() {
    try {
      const exists = await this.client.indices.exists({ index: this.indexName });
      if (!exists) {
        await this.client.indices.create({
          index: this.indexName,
          body: {
            mappings: {
              properties: {
                name: { type: 'text', analyzer: 'ik_max_word' },
                description: { type: 'text', analyzer: 'ik_max_word' },
                price: { type: 'float' },
                stock: { type: 'integer' },
                categories: { type: 'keyword' },
                attributes: {
                  type: 'nested',
                  properties: {
                    name: { type: 'keyword' },
                    value: { type: 'keyword' }
                  }
                },
                created_at: { type: 'date' }
              }
            }
          }
        });
        console.log('索引初始化完成');
      }
    } catch (error) {
      console.error('索引初始化失败:', error);
    }
  }

  async indexProduct(product) {
    try {
      const response = await this.client.index({
        index: this.indexName,
        body: product
      });
      await this.client.indices.refresh({ index: this.indexName });
      return response;
    } catch (error) {
      console.error('商品索引失败:', error);
      throw error;
    }
  }

  async searchProducts(query, filters = {}, page = 1, pageSize = 10) {
    try {
      const from = (page - 1) * pageSize;
      
      const body = {
        query: {
          bool: {
            must: [],
            filter: []
          }
        },
        from,
        size: pageSize,
        sort: [{ _score: 'desc' }, { created_at: 'desc' }]
      };

      // 添加全文搜索条件
      if (query) {
        body.query.bool.must.push({
          multi_match: {
            query,
            fields: ['name^3', 'description^2', 'categories'],
            type: 'best_fields'
          }
        });
      }

      // 添加过滤条件
      if (filters.categories) {
        body.query.bool.filter.push({
          terms: { categories: Array.isArray(filters.categories) ? filters.categories : [filters.categories] }
        });
      }

      if (filters.priceRange) {
        body.query.bool.filter.push({
          range: { price: filters.priceRange }
        });
      }

      // 添加嵌套属性过滤
      if (filters.attributes) {
        filters.attributes.forEach(attr => {
          body.query.bool.filter.push({
            nested: {
              path: 'attributes',
              query: {
                bool: {
                  must: [
                    { term: { 'attributes.name': attr.name } },
                    { term: { 'attributes.value': attr.value } }
                  ]
                }
              }
            }
          });
        });
      }

      const response = await this.client.search({
        index: this.indexName,
        body
      });

      return {
        total: response.hits.total.value,
        products: response.hits.hits.map(hit => ({
          ...hit._source,
          id: hit._id,
          score: hit._score
        }))
      };
    } catch (error) {
      console.error('商品搜索失败:', error);
      throw error;
    }
  }

  async getSuggestions(query) {
    try {
      const response = await this.client.search({
        index: this.indexName,
        body: {
          suggest: {
            name_suggest: {
              prefix: query,
              completion: {
                field: 'name_suggest',
                fuzzy: {
                  fuzziness: 2
                }
              }
            },
            category_suggest: {
              text: query,
              term: {
                field: 'categories'
              }
            }
          }
        }
      });
      
      return {
        nameSuggestions: response.suggest.name_suggest[0].options.map(opt => opt.text),
        categorySuggestions: response.suggest.category_suggest[0].options.map(opt => opt.text)
      };
    } catch (error) {
      console.error('获取建议失败:', error);
      return { nameSuggestions: [], categorySuggestions: [] };
    }
  }
}

// 使用示例
/*
const productSearch = new ProductSearch();
await productSearch.initIndex();

// 添加商品
await productSearch.indexProduct({
  name: 'Apple iPhone 13 Pro',
  description: '最新款iPhone专业版',
  price: 999,
  stock: 100,
  categories: ['phone', 'apple'],
  attributes: [
    { name: 'color', value: 'graphite' },
    { name: 'storage', value: '256GB' }
  ],
  created_at: new Date()
});

// 搜索商品
const results = await productSearch.searchProducts(
  'iphone',
  { 
    categories: 'phone',
    priceRange: { gte: 500, lte: 1200 },
    attributes: [{ name: 'color', value: 'graphite' }]
  },
  1,
  10
);

// 获取搜索建议
const suggestions = await productSearch.getSuggestions('ipho');
*/
11. 错误处理与性能优化
重试机制
javascript 复制代码
const { Client } = require('@elastic/elasticsearch');

const client = new Client({
  node: 'http://localhost:9200',
  maxRetries: 5, // 最大重试次数
  requestTimeout: 60000, // 请求超时时间
  sniffOnStart: true, // 启动时嗅探节点
  sniffInterval: 60000, // 定期嗅探节点
  sniffOnConnectionFault: true // 连接故障时嗅探
});

// 自定义重试策略
client.on('request', (err, result) => {
  if (err) {
    console.error('请求失败:', err.meta ? err.meta.body.error : err.message);
  }
});

// 使用Promise.catch处理错误
async function safeSearch() {
  try {
    const response = await client.search({
      index: 'products',
      body: { query: { match_all: {} } }
    }).catch(err => {
      console.error('搜索失败:', err.meta.body.error);
      throw err;
    });
    return response;
  } catch (error) {
    console.error('捕获到错误:', error);
    throw error;
  }
}
批量操作优化
javascript 复制代码
async function optimizedBulkInsert(documents, batchSize = 1000) {
  try {
    for (let i = 0; i < documents.length; i += batchSize) {
      const batch = documents.slice(i, i + batchSize);
      const body = batch.flatMap(doc => [
        { index: { _index: 'products' } },
        doc
      ]);

      const { body: bulkResponse } = await client.bulk({ body });
      
      if (bulkResponse.errors) {
        console.log(`批量插入批次 ${i / batchSize + 1} 部分失败`);
      } else {
        console.log(`批量插入批次 ${i / batchSize + 1} 成功`);
      }
      
      // 每批处理完成后稍作休息
      if (i + batchSize < documents.length) {
        await new Promise(resolve => setTimeout(resolve, 200));
      }
    }
  } catch (error) {
    console.error('批量插入失败:', error);
    throw error;
  }
}

结语

本文提供了从基础到高级的 Node.js 操作 ElasticSearch 的完整指南,涵盖了索引管理、文档操作、复杂搜索、聚合分析等核心功能,并通过电商商品搜索的实战案例展示了如何在实际项目中应用 ElasticSearch。

希望这些示例代码能帮助您更好地在 Node.js 项目中集成 ElasticSearch。根据实际业务需求,您可以进一步扩展和优化这些代码。

相关推荐
傻小胖6 分钟前
在 Node.js 中使用原生 `http` 模块,获取请求的各个部分:**请求行、请求头、请求体、请求路径、查询字符串** 等内容
网络协议·http·node.js
小橘快跑39 分钟前
Elasticsearch 使用reindex进行数据同步或索引重构
大数据·elasticsearch·重构
何双新1 小时前
L1-4、如何写出清晰有目标的 Prompt
大数据·人工智能·prompt
哲讯智能科技1 小时前
得佳胜&哲讯科技 SAP项目启动会:胶带智造新起点 数字转型新征程
大数据·人工智能
白-胖-子1 小时前
快速认识:数据库、数仓(数据仓库)、数据湖与数据运河
大数据·linux·数据库·数据仓库·人工智能
傻小胖1 小时前
npm的基本使用安装所有包,安装删除指定版本的包,配置命名别名
前端·npm·node.js
傻小胖2 小时前
nodejs使用require导入npm包,开发依赖和生产依赖 ,全局安装
前端·npm·node.js
傻小胖2 小时前
yarn的介绍与操作,yarn和npm的选择
前端·npm·node.js
唐天下文化2 小时前
中国人寿财险广西分公司:金融助推乡村振兴全面发展
大数据·人工智能·金融
TracyCoder1233 小时前
ElasticSearch深入解析(一):Elastic Stack全景
elasticsearch