2025最新:Nest.js + Elasticsearch打造企业级全文检索引擎

2025最新:Nest.js + Elasticsearch打造企业级全文检索引擎

【免费下载链接】nest A progressive Node.js framework for building efficient, scalable, and enterprise-grade server-side applications with TypeScript/JavaScript 🚀 【免费下载链接】nest 项目地址: https://gitcode.com/GitHub_Trending/ne/nest

你还在为项目中的搜索功能低效而烦恼吗?用户抱怨搜索结果不准确?传统数据库模糊查询性能低下?本文将带你从零开始,用Nest.js框架整合Elasticsearch(弹性搜索)构建高性能全文检索系统,解决这些痛点。读完本文你将掌握:

  • Elasticsearch与Nest.js的无缝集成方案
  • 高效索引设计与优化技巧
  • 复杂查询场景的实现方法
  • 生产环境部署最佳实践

技术选型与架构设计

Nest.js作为渐进式Node.js框架,凭借其模块化架构和依赖注入系统,成为构建企业级应用的理想选择。而Elasticsearch作为分布式搜索引擎,提供了强大的全文检索能力和水平扩展特性。两者结合可以打造出高性能、可扩展的搜索系统。

THE 0TH POSITION OF THE ORIGINAL IMAGE

Nest.js的核心优势在于:

  • 基于TypeScript的强类型支持
  • 模块化架构设计,便于维护
  • 丰富的中间件和拦截器系统
  • 与多种数据库和服务的集成能力

项目基本结构如下:

src/
├── modules/
│   ├── search/           # 搜索模块
│   │   ├── search.module.ts
│   │   ├── search.service.ts
│   │   └── search.controller.ts
├── config/               # 配置文件
└── main.ts               # 应用入口

环境准备与依赖安装

首先确保你的开发环境中已安装Node.js(v16+)和npm。通过以下命令创建Nest.js项目:

npm i -g @nestjs/cli
nest new search-app
cd search-app

安装Elasticsearch客户端和相关依赖:

npm install @elastic/elasticsearch @nestjs/config

Elasticsearch服务的安装可以通过Docker快速实现:

docker run -d --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch:8.11.0

核心模块实现

配置模块

创建Elasticsearch配置模块,在src/config/elasticsearch.config.ts中:

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Client } from '@elastic/elasticsearch';

@Injectable()
export class ElasticsearchConfig {
  constructor(private configService: ConfigService) {}

  createClient(): Client {
    return new Client({
      node: this.configService.get('ELASTICSEARCH_NODE') || 'http://localhost:9200',
      auth: {
        username: this.configService.get('ELASTICSEARCH_USERNAME') || 'elastic',
        password: this.configService.get('ELASTICSEARCH_PASSWORD') || 'changeme'
      }
    });
  }
}

搜索服务实现

src/modules/search/search.service.ts中实现核心搜索逻辑:

import { Injectable } from '@nestjs/common';
import { Client } from '@elastic/elasticsearch';
import { ElasticsearchConfig } from '../../config/elasticsearch.config';

@Injectable()
export class SearchService {
  private readonly client: Client;
  private readonly index = 'products';

  constructor(private esConfig: ElasticsearchConfig) {
    this.client = this.esConfig.createClient();
    this.initializeIndex();
  }

  async initializeIndex() {
    const exists = await this.client.indices.exists({ index: this.index });
    if (!exists) {
      await this.client.indices.create({
        index: this.index,
        body: {
          mappings: {
            properties: {
              name: { type: 'text', analyzer: 'ik_max_word' },
              description: { type: 'text', analyzer: 'ik_smart' },
              price: { type: 'float' },
              category: { type: 'keyword' },
              createdAt: { type: 'date' }
            }
          }
        }
      });
    }
  }

  async indexDocument(document: any) {
    return this.client.index({
      index: this.index,
      document: {
        ...document,
        createdAt: new Date()
      }
    });
  }

  async search(query: string, page = 1, limit = 10) {
    const { body } = await this.client.search({
      index: this.index,
      from: (page - 1) * limit,
      size: limit,
      body: {
        query: {
          multi_match: {
            query,
            fields: ['name^3', 'description'],
            fuzziness: 'AUTO'
          }
        },
        highlight: {
          fields: {
            name: {},
            description: {}
          }
        }
      }
    });

    return {
      total: body.hits.total.value,
      hits: body.hits.hits.map(hit => ({
        id: hit._id,
        ...hit._source,
        highlight: hit.highlight
      })),
      page,
      limit
    };
  }
}

控制器实现

创建搜索控制器处理HTTP请求,在src/modules/search/search.controller.ts中:

import { Controller, Get, Post, Body, Query } from '@nestjs/common';
import { SearchService } from './search.service';

@Controller('search')
export class SearchController {
  constructor(private readonly searchService: SearchService) {}

  @Post('index')
  async indexDocument(@Body() document: any) {
    return this.searchService.indexDocument(document);
  }

  @Get()
  async search(
    @Query('q') query: string,
    @Query('page') page = 1,
    @Query('limit') limit = 10
  ) {
    return this.searchService.search(query, +page, +limit);
  }
}

模块注册

在搜索模块中注册服务和控制器,在src/modules/search/search.module.ts中:

import { Module } from '@nestjs/common';
import { SearchService } from './search.service';
import { SearchController } from './search.controller';
import { ElasticsearchConfig } from '../../config/elasticsearch.config';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [ConfigModule],
  controllers: [SearchController],
  providers: [SearchService, ElasticsearchConfig]
})
export class SearchModule {}

高级功能实现

搜索建议功能

为提升用户体验,添加搜索建议功能:

async getSuggestions(query: string) {
  const { body } = await this.client.search({
    index: this.index,
    body: {
      suggest: {
        text: query,
        termSuggestions: {
          term: {
            field: 'name',
            suggest_mode: 'popular',
            max_edits: 2
          }
        }
      }
    }
  });
  
  return body.suggest.termSuggestions[0].options.map(option => option.text);
}

过滤与聚合查询

实现按分类过滤和价格区间统计:

async filteredSearch(query: string, filters: any) {
  const mustClauses = query 
    ? [{
        multi_match: {
          query,
          fields: ['name', 'description']
        }
      }]
    : [{ match_all: {} }];
    
  if (filters.category) {
    mustClauses.push({
      term: { category: filters.category }
    });
  }
  
  const rangeClause = filters.priceMin || filters.priceMax 
    ? {
        range: {
          price: {}
        }
      }
    : null;
    
  if (filters.priceMin) rangeClause.range.price.gte = filters.priceMin;
  if (filters.priceMax) rangeClause.range.price.lte = filters.priceMax;
  
  if (rangeClause) mustClauses.push(rangeClause);

  const { body } = await this.client.search({
    index: this.index,
    body: {
      query: {
        bool: { must: mustClauses }
      },
      aggs: {
        categories: {
          terms: { field: 'category' }
        },
        price_ranges: {
          range: {
            field: 'price',
            ranges: [
              { to: 100 },
              { from: 100, to: 500 },
              { from: 500 }
            ]
          }
        }
      }
    }
  });
  
  return {
    total: body.hits.total.value,
    hits: body.hits.hits.map(hit => hit._source),
    aggregations: body.aggregations
  };
}

性能优化策略

索引优化

  1. 合理设计分片和副本数量
  2. 使用合适的分析器(如IK分词器处理中文)
  3. 优化字段类型和映射

查询优化

// 使用过滤器缓存
async optimizedSearch(query: string) {
  return this.client.search({
    index: this.index,
    body: {
      query: {
        bool: {
          must: [{ match: { name: query } }],
          filter: [{ term: { active: true } }]
        }
      }
    }
  });
}

连接池配置

优化Elasticsearch客户端连接:

createClient(): Client {
  return new Client({
    node: this.configService.get('ELASTICSEARCH_NODE'),
    auth: {
      username: this.configService.get('ELASTICSEARCH_USERNAME'),
      password: this.configService.get('ELASTICSEARCH_PASSWORD')
    },
    maxRetries: 3,
    requestTimeout: 30000,
    sniffOnStart: true,
    pool: {
      max: 10,
      min: 2
    }
  });
}

部署与监控

生产环境配置

创建环境配置文件.env.production

ELASTICSEARCH_NODE=http://elasticsearch:9200
ELASTICSEARCH_USERNAME=elastic
ELASTICSEARCH_PASSWORD=your_secure_password
NODE_ENV=production
PORT=3000

Docker部署

创建Dockerfile

FROM node:18-alpine As development
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

FROM node:18-alpine As production
WORKDIR /app
COPY --from=development /app/node_modules ./node_modules
COPY --from=development /app/package*.json ./
COPY --from=development /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/main"]

创建docker-compose.yml

version: '3'
services:
  app:
    build:
      context: .
      target: production
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - ELASTICSEARCH_NODE=http://elasticsearch:9200
    depends_on:
      - elasticsearch
  elasticsearch:
    image: elasticsearch:8.11.0
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
    ports:
      - "9200:9200"
    volumes:
      - esdata:/usr/share/elasticsearch/data
volumes:
  esdata:

监控与日志

集成Nest.js日志系统:

import { Logger } from '@nestjs/common';

private readonly logger = new Logger(SearchService.name);

async search(query: string) {
  this.logger.log(`Search query: ${query}`);
  try {
    // 搜索逻辑
  } catch (error) {
    this.logger.error(`Search failed: ${error.message}`, error.stack);
    throw error;
  }
}

总结与展望

本文详细介绍了如何使用Nest.js和Elasticsearch构建企业级全文检索系统,包括环境搭建、核心功能实现、性能优化和部署流程。通过这种架构,可以轻松应对百万级数据量的搜索需求,为用户提供快速、准确的搜索体验。

未来可以进一步扩展:

  • 添加搜索结果排序算法
  • 实现分布式索引
  • 集成机器学习模型优化搜索相关性
  • 开发实时搜索功能

希望本文对你的项目有所帮助!如果觉得有用,请点赞收藏并关注作者获取更多技术干货。下一篇我们将探讨Elasticsearch的高级索引优化技巧。

完整示例代码 官方文档

【免费下载链接】nest A progressive Node.js framework for building efficient, scalable, and enterprise-grade server-side applications with TypeScript/JavaScript 🚀 【免费下载链接】nest 项目地址: https://gitcode.com/GitHub_Trending/ne/nest

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值