首页 › tutorials › Claude Code全文搜索实战:ES从索引到查询

概述

本教程面向有 Node.js 基础的开发者,手把手教你用 Claude Code 从零构建一个基于 Elasticsearch 的全文搜索服务。内容覆盖索引设计、中文分词、批量导入、多字段检索、聚合分析、前后端联调全链路,最终交付一个可运行的 RESTful 搜索 API + 搜索建议接口。学完后,你能独立为任何项目接入生产级搜索能力。

适用人群:有 Node.js/TypeScript 基础,了解 REST API,希望掌握搜索引擎开发的工程师。

学习目标: - 理解 Elasticsearch 核心概念(索引、映射、分词、聚合) - 用 Claude Code 快速搭建搜索服务后端 - 掌握中文分词与多字段搜索的最佳实践 - 完成前后端联调与性能调优


前置条件

工具 版本要求 用途
Claude Code 最新版 AI 辅助编码
Node.js ≥ 20.x 运行时环境
Docker ≥ 24.x 运行 Elasticsearch
curl 或 HTTPie 任意 API 测试
Git ≥ 2.40 版本管理

确保 Docker 已启动,至少分配 4GB 内存给 Docker。


第一步:项目初始化与 ES 环境

1.1 创建项目骨架

打开终端,启动 Claude Code:

mkdir es-search-service && cd es-search-service
claude

输入提示词:

初始化一个 Node.js + TypeScript 项目,使用 pnpm,包含以下依赖:
- @elastic/elasticsearch(ES 官方客户端)
- express(Web 框架)
- zod(参数校验)
- pino(日志)
项目结构采用 src 目录,入口为 src/index.ts,配置使用 dotenv。
同时创建 .env.example 文件,包含 ES_HOST、ES_PORT、PORT 变量。

Claude Code 会生成如下结构:

es-search-service/
├── src/
│   ├── index.ts          # 应用入口
│   ├── config.ts         # 配置管理
│   ├── es-client.ts      # ES 客户端封装
│   └── routes/
│       └── search.ts     # 搜索路由
├── package.json
├── tsconfig.json
├── .env.example
└── docker-compose.yml

1.2 Docker 启动 Elasticsearch

让 Claude Code 生成 docker-compose.yml:

# docker-compose.yml
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.17.0
    container_name: es-dev
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - ES_JAVA_OPTS=-Xms1g -Xmx1g
    ports:
      - "9200:9200"
    volumes:
      - es-data:/usr/share/elasticsearch/data

  kibana:
    image: docker.elastic.co/kibana/kibana:8.17.0
    container_name: kibana-dev
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    ports:
      - "5601:5601"
    depends_on:
      - elasticsearch

volumes:
  es-data:

启动服务:

docker compose up -d
# 等待约 30 秒后验证
curl http://localhost:9200

预期输出:

{
  "name" : "es-dev",
  "cluster_name" : "docker-cluster",
  "version" : {
    "number" : "8.17.0"
  },
  "tagline" : "You Know, for Search"
}

常见问题:

问题 原因 解决方案
max virtual memory areas vm.max_map_count过低 Linux 内核限制 sudo sysctl -w vm.max_map_count=262144
容器启动后立即退出 内存不足 Docker 设置至少 4GB 内存
连接被拒绝 ES 还在初始化 等待 30-60 秒后重试

第二步:ES 客户端封装与索引设计

2.1 客户端封装

请编写 src/es-client.ts,封装 Elasticsearch 客户端:
1. 从 .env 读取 ES_HOST 和 ES_PORT
2. 单例模式导出客户端实例
3. 提供 healthCheck() 方法验证连接
4. 使用 pino 记录连接状态

Claude Code 生成的核心代码:

// src/es-client.ts
import { Client } from '@elastic/elasticsearch';
import pino from 'pino';

const logger = pino({ name: 'es-client' });

let client: Client | null = null;

export function getESClient(): Client {
  if (!client) {
    client = new Client({
      node: `${process.env.ES_HOST || 'http://localhost'}:${process.env.ES_PORT || 9200}`,
      requestTimeout: 30000,
      maxRetries: 3,
    });
    logger.info('Elasticsearch client created');
  }
  return client;
}

export async function healthCheck(): Promise<boolean> {
  try {
    const result = await getESClient().cluster.health();
    logger.info({ status: result.status }, 'ES cluster health');
    return result.status !== 'red';
  } catch (error) {
    logger.error({ error }, 'ES health check failed');
    return false;
  }
}

2.2 索引映射设计

搜索服务的核心是索引映射(Mapping)。我们以一个商品搜索场景为例:

请创建 src/index-setup.ts,定义一个 products 索引:
- 字段包括:title(标题)、description(描述)、category(分类)、
  price(价格)、brand(品牌)、tags(标签数组)、rating(评分)、
  createdAt(创建时间)
- title 和 description 使用 ik_max_word 分词器(中文)
- category 使用 keyword 类型用于精确过滤
- price 使用 double 类型用于范围查询
- 设置一个自定义分析器 product_analyzer,使用 ik_smart 分词
  加上 lowercase 和 ascii_folding token filter

Claude Code 生成:

// src/index-setup.ts
import { getESClient } from './es-client';
import pino from 'pino';

const logger = pino({ name: 'index-setup' });

const PRODUCTS_INDEX = 'products';

const productsMapping = {
  settings: {
    analysis: {
      analyzer: {
        product_analyzer: {
          tokenizer: 'ik_smart',
          filter: ['lowercase', 'asciifolding'],
        },
      },
    },
  },
  mappings: {
    properties: {
      title: {
        type: 'text',
        analyzer: 'ik_max_word',
        search_analyzer: 'ik_smart',
        fields: {
          keyword: { type: 'keyword' },
          pinyin: {
            type: 'text',
            analyzer: 'pinyin',
          },
        },
      },
      description: {
        type: 'text',
        analyzer: 'ik_max_word',
        search_analyzer: 'ik_smart',
      },
      category: { type: 'keyword' },
      price: { type: 'double' },
      brand: {
        type: 'text',
        analyzer: 'ik_smart',
        fields: {
          keyword: { type: 'keyword' },
        },
      },
      tags: { type: 'keyword' },
      rating: { type: 'float' },
      createdAt: { type: 'date' },
    },
  },
};

export async function createProductsIndex(): Promise<void> {
  const es = getESClient();
  const exists = await es.indices.exists({ index: PRODUCTS_INDEX });

  if (exists) {
    logger.info(`Index "${PRODUCTS_INDEX}" already exists, skipping`);
    return;
  }

  await es.indices.create({
    index: PRODUCTS_INDEX,
    body: productsMapping,
  });

  logger.info(`Index "${PRODUCTS_INDEX}" created successfully`);
}

export async function deleteProductsIndex(): Promise<void> {
  const es = getESClient();
  await es.indices.delete({ index: PRODUCTS_INDEX }, { ignore: [404] });
  logger.info(`Index "${PRODUCTS_INDEX}" deleted`);
}

关键设计决策解释:


第三步:批量数据导入

3.1 生成测试数据

创建 src/seed.ts,包含:
1. 一个包含 200 条模拟商品数据的数组(中文商品名和描述)
2. 使用 ES bulk API 批量导入,每批 50 条
3. 导入完成后打印统计信息

核心批量导入逻辑:

// src/seed.ts(节选)
import { getESClient } from './es-client';
import { generateProducts } from './mock-data';

const BATCH_SIZE = 50;
const PRODUCTS_INDEX = 'products';

export async function seedProducts(): Promise<void> {
  const es = getESClient();
  const products = generateProducts(200);
  logger.info(`Seeding ${products.length} products...`);

  for (let i = 0; i < products.length; i += BATCH_SIZE) {
    const batch = products.slice(i, i + BATCH_SIZE);
    const body = batch.flatMap((doc) => [
      { index: { _index: PRODUCTS_INDEX, _id: doc.id } },
      doc,
    ]);

    const result = await es.bulk({ body, refresh: 'wait_for' });

    if (result.errors) {
      const errors = result.items
        .filter((item) => item.index?.error)
        .map((item) => item.index?.error);
      logger.error({ errors }, 'Bulk indexing errors');
    }
  }

  const count = await es.count({ index: PRODUCTS_INDEX });
  logger.info(`Total documents: ${count.count}`);
}

3.2 注意事项


第四步:核心搜索 API

这是搜索服务的核心部分。让 Claude Code 一次性生成完整的搜索路由:

创建 src/routes/search.ts,实现以下 5 个 API 端点:

1. GET /api/search - 全文搜索
   参数:q(关键词)、category(分类过滤)、minPrice/maxPrice(价格范围)、
         page/size(分页)、sort(排序字段)
   返回:命中文档列表 + 总数 + 聚合结果

2. GET /api/suggest - 搜索建议(自动补全)
   参数:prefix(前缀)
   返回:最多 10 个建议词

3. GET /api/products/:id - 获取单个商品详情

4. POST /api/products - 创建商品

5. PUT /api/products/:id - 更新商品

使用 zod 做参数校验。搜索 API 要实现 multi_match 查询,
同时搜索 title 和 description 字段,并用 function_score 
根据 rating 做加权排序。聚合要包含按 category 分组的桶聚合
和 price 的统计聚合。

Claude Code 生成的搜索核心逻辑:

// src/routes/search.ts(核心搜索逻辑节选)
import { z } from 'zod';
import { getESClient } from '../es-client';
import { Request, Response, Router } from 'express';

const SearchQuerySchema = z.object({
  q: z.string().min(1),
  category: z.string().optional(),
  minPrice: z.coerce.number().min(0).optional(),
  maxPrice: z.coerce.number().min(0).optional(),
  page: z.coerce.number().min(1).default(1),
  size: z.coerce.number().min(1).max(100).default(20),
  sort: z.enum(['_score', 'price', 'rating', 'createdAt']).default('_score'),
});

export async function fullTextSearch(req: Request, res: Response) {
  const parsed = SearchQuerySchema.safeParse(req.query);
  if (!parsed.success) {
    return res.status(400).json({ error: parsed.error.flatten() });
  }

  const { q, category, minPrice, maxPrice, page, size, sort } = parsed.data;
  const es = getESClient();
  const from = (page - 1) * size;

  // 构建 bool 查询
  const must: any[] = [
    {
      multi_match: {
        query: q,
        fields: ['title^3', 'description^1', 'brand^2'],
        type: 'best_fields',
        fuzziness: 'AUTO',
      },
    },
  ];

  const filter: any[] = [];
  if (category) filter.push({ term: { category } });
  if (minPrice !== undefined || maxPrice !== undefined) {
    const range: any = {};
    if (minPrice !== undefined) range.gte = minPrice;
    if (maxPrice !== undefined) range.lte = maxPrice;
    filter.push({ range: { price: range } });
  }

  const result = await es.search({
    index: 'products',
    body: {
      from,
      size,
      query: {
        function_score: {
          query: { bool: { must, filter } },
          functions: [
            {
              field_value_factor: {
                field: 'rating',
                factor: 1.2,
                modifier: 'sqrt',
                missing: 1,
              },
            },
          ],
          boost_mode: 'multiply',
        },
      },
      sort: sort === '_score' ? undefined : [{ [sort]: 'desc' }],
      highlight: {
        fields: {
          title: { pre_tags: ['<em>'], post_tags: ['</em>'] },
          description: {
            fragment_size: 150,
            number_of_fragments: 3,
          },
        },
      },
      aggs: {
        categories: { terms: { field: 'category', size: 20 } },
        price_stats: { stats: { field: 'price' } },
        brands: { terms: { field: 'brand.keyword', size: 10 } },
      },
    },
  });

  res.json({
    total: result.hits.total,
    page,
    size,
    results: result.hits.hits.map((hit: any) => ({
      id: hit._id,
      score: hit._score,
      ...hit._source,
      highlight: hit.highlight,
    })),
    aggregations: {
      categories: result.aggregations?.categories,
      priceStats: result.aggregations?.price_stats,
      brands: result.aggregations?.brands,
    },
  });
}

搜索 API 设计亮点:


第五步:搜索建议(自动补全)

搜索建议是提升用户体验的关键功能:

// src/routes/search.ts(建议接口节选)
export async function searchSuggest(req: Request, res: Response) {
  const { prefix } = req.query;
  if (!prefix || typeof prefix !== 'string') {
    return res.status(400).json({ error: 'prefix is required' });
  }

  const es = getESClient();

  const result = await es.search({
    index: 'products',
    body: {
      size: 0,
      query: {
        prefix: { 'title.keyword': { value: prefix.toLowerCase() } },
      },
      aggs: {
        suggestions: {
          terms: {
            field: 'title.keyword',
            size: 10,
            order: { _count: 'desc' },
          },
        },
      },
    },
  });

  const suggestions = result.aggregations?.suggestions?.buckets?.map(
    (b: any) => ({ text: b.key, count: b.doc_count })
  ) || [];

  res.json({ suggestions });
}

进阶方案:对于大型数据集,应使用 ES 的 Completion Suggester 或专门的 Search-as-you-type 字段类型:

// 在映射中添加 suggest 字段
suggest: {
  type: 'completion',
  analyzer: 'ik_smart',
  contexts: [
    { name: 'category', type: 'category' },
  ],
}

// 查询时使用
await es.search({
  index: 'products',
  body: {
    suggest: {
      product_suggest: {
        prefix,
        completion: {
          field: 'suggest',
          size: 10,
          fuzzy: { fuzziness: 'AUTO' },
        },
      },
    },
  },
});

第六步:应用入口与路由挂载

// src/index.ts
import express from 'express';
import dotenv from 'dotenv';
import { getESClient, healthCheck } from './es-client';
import { createProductsIndex } from './index-setup';
import { seedProducts } from './seed';
import { searchRouter } from './routes/search';
import pino from 'pino';

dotenv.config();

const logger = pino({ name: 'app' });
const app = express();
app.use(express.json());

// 挂载路由
app.use('/api', searchRouter);

// 健康检查
app.get('/health', async (_req, res) => {
  const esHealthy = await healthCheck();
  res.json({ status: esHealthy ? 'ok' : 'degraded', es: esHealthy });
});

const PORT = process.env.PORT || 3000;

async function bootstrap() {
  const healthy = await healthCheck();
  if (!healthy) {
    logger.error('Cannot connect to Elasticsearch, exiting');
    process.exit(1);
  }

  await createProductsIndex();
  logger.info('Index ready');

  app.listen(PORT, () => {
    logger.info(`Search service running on port ${PORT}`);
  });
}

bootstrap().catch((err) => {
  logger.fatal(err, 'Bootstrap failed');
  process.exit(1);
});

启动并测试

# 安装依赖并启动
pnpm install
pnpm dev

# 测试搜索
curl "http://localhost:3000/api/search?q=无线耳机&category=数码&minPrice=100&maxPrice=2000"

# 测试搜索建议
curl "http://localhost:3000/api/suggest?prefix=蓝牙"

# 健康检查
curl http://localhost:3000/health

第七步:前端搜索 UI 集成

让 Claude Code 生成一个搜索前端组件:

创建 public/index.html,包含一个搜索页面:
- 搜索框带自动补全(防抖 300ms)
- 搜索结果列表,标题高亮命中关键词
- 左侧分类筛选面板(从聚合结果生成)
- 价格范围滑块
- 分页导航
- 使用 Tailwind CSS CDN,纯 HTML/JS 实现,无框架依赖

关键前端代码:

<!-- public/index.html(搜索核心 JS 节选)-->
<script>
const DEBOUNCE_MS = 300;
let searchTimer;

document.getElementById('searchInput').addEventListener('input', (e) => {
  clearTimeout(searchTimer);
  const query = e.target.value.trim();

  if (query.length === 0) {
    clearResults();
    return;
  }

  searchTimer = setTimeout(async () => {
    const params = new URLSearchParams({
      q: query,
      category: selectedCategory || '',
      minPrice: priceRange[0],
      maxPrice: priceRange[1],
      page: currentPage,
      size: 20,
    });

    const res = await fetch(`/api/search?${params}`);
    const data = await res.json();

    renderResults(data.results);
    renderFacets(data.aggregations);
    renderPagination(data.total, data.page, data.size);
  }, DEBOUNCE_MS);
});

// 自动补全
document.getElementById('searchInput').addEventListener('keyup', (e) => {
  if (e.key === 'Enter') return;
  const prefix = e.target.value.trim();
  if (prefix.length < 2) return;

  fetch(`/api/suggest?prefix=${encodeURIComponent(prefix)}`)
    .then((r) => r.json())
    .then((data) => renderSuggestions(data.suggestions));
});

function renderResults(hits) {
  const html = hits.map((hit) => `
    <div class="border rounded-lg p-4 hover:shadow-md transition">
      <h3 class="text-lg font-semibold">${hit.highlight?.title?.[0] || hit.title}</h3>
      <p class="text-gray-600 mt-1">
        ${hit.highlight?.description?.[0] || hit.description?.slice(0, 150)}...
      </p>
      <div class="flex justify-between mt-2">
        <span class="text-xl font-bold text-red-600">¥${hit.price}</span>
        <span class="text-yellow-500">★ ${hit.rating}</span>
      </div>
    </div>
  `).join('');
  document.getElementById('results').innerHTML = html;
}
</script>

实战案例:完整商品搜索服务

把前面的所有步骤组合起来,我们得到了一个完整的商品搜索服务。整体架构如下:

graph TB
    subgraph Frontend["前端层"]
        UI["搜索页面<br/>Tailwind CSS"]
        AC["自动补全<br/>300ms 防抖"]
    end

    subgraph API["API 层 (Express)"]
        SEARCH["GET /api/search<br/>全文搜索"]
        SUGGEST["GET /api/suggest<br/>搜索建议"]
        CRUD["POST/PUT /api/products<br/>CRUD"]
        HEALTH["GET /health<br/>健康检查"]
    end

    subgraph ES["Elasticsearch 8.x"]
        IDX["products 索引"]
        IK["IK 中文分词器"]
        AGG["聚合引擎"]
        BULK["Bulk API"]
    end

    subgraph Data["数据层"]
        SEED["Mock 数据<br/>200 条商品"]
    end

    UI --> AC
    AC --> SUGGEST
    UI --> SEARCH
    SEARCH --> IDX
    SUGGEST --> IDX
    CRUD --> IDX
    IDX --> IK
    IDX --> AGG
    SEED --> BULK
    BULK --> IDX
    HEALTH --> ES

Claude Code 辅助开发流程

sequenceDiagram
    participant D as 开发者
    participant CC as Claude Code
    participant ES as Elasticsearch
    participant Git as Git

    D->>CC: 描述搜索服务需求
    CC->>CC: 生成项目骨架 + 配置
    CC->>ES: 创建索引 + 映射
    D->>CC: 提出中文分词需求
    CC->>ES: 配置 IK 分词器
    CC->>ES: 批量导入测试数据
    D->>CC: 要求实现搜索 API
    CC->>CC: 生成 multi_match 查询
    D->>CC: 要求前端集成
    CC->>CC: 生成搜索 UI
    D->>CC: /commit
    CC->>Git: 提交代码
    D->>CC: 要求性能优化
    CC->>ES: 分析慢查询
    CC->>ES: 调整索引设置

性能优化 Checklist

让 Claude Code 逐一帮你检查:

# 1. 查看索引健康与分片状态
curl localhost:9200/_cat/indices?v

# 2. 分析查询性能
curl -X POST "localhost:9200/products/_search?explain=true" -H 'Content-Type: application/json' -d '{
  "query": { "match": { "title": "无线耳机" } },
  "profile": true
}'

# 3. 查看分词效果
curl -X POST "localhost:9200/products/_analyze" -H 'Content-Type: application/json' -d '{
  "analyzer": "ik_max_word",
  "text": "索尼无线蓝牙降噪耳机"
}'

# 4. 查看索引映射
curl localhost:9200/products/_mapping?pretty

关键优化项:

优化方向 具体措施 效果
查询缓存 设置 index.queries.cache.enabled: true 重复查询提速 10×
字段存储 对不参与搜索的字段设置 "index": false 减少索引体积 30%
批量大小 bulk 批次从 50 调到 500 写入吞吐提升 5×
分片策略 单分片不超过 50GB 文档 保持搜索延迟 < 100ms
高亮优化 使用 unified 高亮器 高亮计算速度提升 3×

常见问题与排错

问题 1:IK 分词器未安装

现象:创建索引报错 unknown analyzer [ik_max_word]

原因:默认 ES 镜像不含 IK 分词器插件

解决:修改 docker-compose.yml:

elasticsearch:
  image: docker.elastic.co/elasticsearch/elasticsearch:8.17.0
  # 添加插件安装命令
  command: >
    bash -c "
      elasticsearch-plugin install --batch https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v8.17.0/elasticsearch-analysis-ik-8.17.0.zip
      && /usr/local/bin/docker-entrypoint.sh
    "

或让 Claude Code 自动处理:

ES 创建索引时报错 ik_max_word 找不到,请帮我修改 docker-compose.yml 
安装 elasticsearch-analysis-ik 8.17.0 插件,并重新启动容器

问题 2:搜索结果为空

排错步骤(直接让 Claude Code 执行):

我的搜索 API 返回空结果,请帮我排查:
1. 检查索引中是否有文档(_count API)
2. 验证分词器输出是否正确(_analyze API)
3. 用 _search API 直接测试查询语句
4. 检查 refresh interval 设置

问题 3:bulk 导入部分失败

// 排错代码:打印失败的文档 ID
if (result.errors) {
  result.items.forEach((item, idx) => {
    if (item.index?.error) {
      logger.error({
        id: item.index._id,
        error: item.index.error,
        batchIndex: idx,
      }, 'Failed to index document');
    }
  });
}

进阶主题

向量搜索(语义检索)

Elasticsearch 8.x 内置向量搜索能力,可以结合语义检索提升搜索质量:

// 先生成文本 embedding(使用 ES 内置的 inference API)
// 然后用 knn 搜索
const result = await es.search({
  index: 'products',
  body: {
    knn: {
      field: 'title_embedding',
      query_vector: embedding,  // 768 维向量
      k: 10,
      num_candidates: 100,
    },
    // 可以和传统全文搜索组合
    query: {
      bool: {
        should: [
          { multi_match: { query: q, fields: ['title^3', 'description'] } },
        ],
      },
    },
    rank: {
      rrf: { window_size: 100, rank_constant: 60 },
    },
  },
});

这实现了 混合搜索(关键词 + 语义向量 + RRF 排序融合),是目前搜索系统的主流架构。

搜索日志分析

// 记录搜索行为到 ES 专门索引
export async function logSearch(query: string, resultCount: number, latencyMs: number) {
  const es = getESClient();
  await es.index({
    index: 'search_logs',
    body: {
      query,
      resultCount,
      latencyMs,
      timestamp: new Date().toISOString(),
    },
  });
}

后续可在 Kibana 中分析热门搜索词、零结果查询、搜索延迟分布。


训练营适用性评估

维度 评估
适用训练营类型 全栈开发训练营、搜索引擎专题营、Node.js 进阶营
教学时长 4-6 小时(含实操)
难度等级 中等偏上(需 ES 基础概念)
前置要求 Node.js + TypeScript + Docker 基础
分组建议 2-3 人一组,每组完成一个垂直领域的搜索服务
考核方式 搜索准确率测试 + API 响应延迟 + 代码质量
延伸项目 添加向量搜索、接入 LLM 做查询理解、多语言搜索

参考来源

  1. Elasticsearch JavaScript Client Getting Started — ES 官方 Node.js 客户端文档
  2. How to Use Elasticsearch in JavaScript - Elasticsearch Labs — 生产级 ES+JS 最佳实践系列
  3. Connect to external tools with MCP - Claude Code Docs — Claude Code MCP 集成指南
  4. Elasticsearch Skill for Claude Code — Claude Code ES 技能参考
  5. Elasticsearch Integration with Node.js Applications — Node.js + ES 集成教程

[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。

51学通信知识星球海报-扫码加入