系统设计文档.md 28.2 KB

搜索引擎通用化开发进度

项目概述

对后端搜索技术 做通用化。 通用化的本质 是 对于各种业务数据、各种检索需求,都可以 用少量定制+配置化 来实现效果。

通用化的本质:对于各种业务数据、各种检索需求,都可以用少量定制+配置化来实现效果。


1. 原始数据层的约定

1.1 店匠主表

所有租户共用以下主表:

  • shoplazza_product_sku - SKU级别商品数据
  • shoplazza_product_spu - SPU级别商品数据

1.2 索引结构(SPU维度)

统一索引架构

  • 所有客户共享同一个Elasticsearch索引:search_products
  • 索引粒度:SPU级别(每个文档代表一个SPU)
  • 数据隔离:通过tenant_id字段实现租户隔离
  • 嵌套结构:每个SPU文档包含嵌套的skus数组

索引文档结构

{
  "tenant_id": "1",
  "spu_id": "123",
  "title_zh": "蓝牙耳机",
  "title_en": "Bluetooth Headphones",
  "brief_zh": "高品质蓝牙耳机",
  "brief_en": "High-quality Bluetooth headphones",
  "category_name": "电子产品",
  "category_path_zh": "电子产品/音频设备/耳机",
  "category_path_en": "Electronics/Audio/Headphones",
  "category1_name": "电子产品",
  "category2_name": "音频设备",
  "category3_name": "耳机",
  "vendor_zh": "品牌A",
  "vendor_en": "Brand A",
  "min_price": 199.99,
  "max_price": 299.99,
  "option1_name": "color",
  "option2_name": "size",
  "specifications": [
    {
      "sku_id": "456",
      "name": "color",
      "value": "black"
    },
    {
      "sku_id": "456",
      "name": "size",
      "value": "large"
    }
  ],
  "skus": [
    {
      "sku_id": "456",
      "price": 199.99,
      "compare_at_price": 249.99,
      "sku_code": "SKU-123-1",
      "stock": 50,
      "weight": 0.2,
      "weight_unit": "kg",
      "option1_value": "black",
      "option2_value": "large",
      "option3_value": null,
      "image_src": "https://example.com/image.jpg"
    }
  ],
  "title_embedding": [0.1, 0.2, ...],  // 1024维向量
  "image_embedding": [
    {
      "vector": [0.1, 0.2, ...],  // 1024维向量
      "url": "https://example.com/image.jpg"
    }
  ]
}

1.3 索引结构简化方案

简化原则

  • 硬编码映射:ES mapping 结构直接定义在 JSON 文件中(mappings/search_products.json
  • 统一索引结构:所有租户共享相同的索引结构,通过 tenant_id 隔离数据
  • 数据源统一:所有租户使用相同的 MySQL 表结构(店匠标准表)
  • 查询配置硬编码:查询相关配置(字段 boost、查询域等)硬编码在 search/query_config.py

索引结构特点

  1. 多语言字段:所有文本字段支持中英文(title_zh/en, brief_zh/en, description_zh/en, vendor_zh/en, category_path_zh/en, category_name_zh/en
  2. 嵌套字段
    • skus: SKU 嵌套数组(包含价格、库存、选项值等)
    • specifications: 规格嵌套数组(包含 name、value、sku_id)
    • image_embedding: 图片向量嵌套数组
  3. 扁平化字段sku_prices, sku_weights, total_inventory 等用于过滤和排序
  4. 向量字段title_embedding(1024维)用于语义搜索

实现文件

  • mappings/search_products.json - ES mapping 定义(硬编码)
  • search/query_config.py - 查询配置(硬编码)
  • indexer/mapping_generator.py - 加载 JSON mapping 并创建索引

2. 索引结构实现

2.1 硬编码映射方案

实现方式

  • ES mapping 直接定义在 mappings/search_products.json 文件中
  • 所有租户共享相同的索引结构
  • 查询配置硬编码在 search/query_config.py

索引字段结构

基础字段

  • tenant_id (keyword): 租户ID,用于数据隔离
  • spu_id (keyword): SPU唯一标识
  • create_time, update_time (date): 创建和更新时间

多语言文本字段

  • title_zh/en (text): 标题(中英文)
  • brief_zh/en (text): 短描述(中英文)
  • description_zh/en (text): 详细描述(中英文)
  • vendor_zh/en (text): 供应商/品牌(中英文)
  • category_path_zh/en (text): 类目路径(中英文)
  • category_name_zh/en (text): 类目名称(中英文)

分析器配置

  • 中文字段:hanlp_index(索引时)/ hanlp_standard(查询时)
  • 英文字段:english
  • vendor 字段包含 keyword 子字段(normalizer: lowercase)

分类字段

  • category_id (keyword): 类目ID
  • category_name (keyword): 类目名称
  • category_level (integer): 类目层级
  • category1_name, category2_name, category3_name (keyword): 多级类目名称

规格字段(Specifications)

  • specifications (nested): 规格嵌套数组
    • sku_id (keyword): SKU ID
    • name (keyword): 规格名称(如 "color", "size")
    • value (keyword): 规格值(如 "white", "256GB")

用途

  • 支持按规格过滤:{"specifications": {"name": "color", "value": "white"}}
  • 支持规格分面:["specifications"]["specifications.color"]

SKU嵌套字段

  • skus (nested): SKU嵌套数组
    • sku_id, price, compare_at_price, sku_code
    • stock, weight, weight_unit
    • option1_value, option2_value, option3_value
    • image_src (index: false)

选项名称字段

  • option1_name, option2_name, option3_name (keyword): 选项名称(如 "color", "size")

扁平化字段

  • min_price, max_price, compare_at_price (float): 价格字段
  • sku_prices (float[]): 所有SKU价格数组
  • sku_weights (long[]): 所有SKU重量数组
  • total_inventory (long): 总库存

向量字段

  • title_embedding (dense_vector, 1024维): 标题向量,用于语义搜索
  • image_embedding (nested): 图片向量数组
    • vector (dense_vector, 1024维)
    • url (text)

实现模块

  • mappings/search_products.json - ES mapping 定义
  • indexer/mapping_generator.py - 加载 JSON mapping 并创建索引
  • search/query_config.py - 查询配置(字段 boost、查询域等)

2.2 索引结构配置(查询域配置)

配置内容:定义了 ES 的字段索引 mapping 配置,支持各个域的查询,包括默认域的查询。

实现情况

域(Domain)配置

每个域定义了:

  • 域名称(如 default, title, category, brand
  • 域标签(中文描述)
  • 搜索字段列表
  • 默认分析器
  • 权重(boost)
  • 多语言字段映射language_field_mapping

多语言字段映射

支持将不同语言的查询路由到对应的字段:

indexes:
  - name: "default"
    label: "默认索引"
    fields:
      - "name"
      - "enSpuName"
      - "ruSkuName"
      - "categoryName"
      - "brandName"
    analyzer: "chinese_ecommerce"
    boost: 1.0
    language_field_mapping:
      zh:
        - "name"
        - "categoryName"
        - "brandName"
      en:
        - "enSpuName"
      ru:
        - "ruSkuName"

  - name: "title"
    label: "标题索引"
    fields:
      - "name"
      - "enSpuName"
      - "ruSkuName"
    analyzer: "chinese_ecommerce"
    boost: 2.0
    language_field_mapping:
      zh:
        - "name"
      en:
        - "enSpuName"
      ru:
        - "ruSkuName"

工作原理

  1. 检测查询语言(中文、英文、俄文等)
  2. 如果查询语言在 language_field_mapping 中,使用原始查询搜索对应语言的字段
  3. 将查询翻译到其他支持的语言,分别搜索对应语言的字段
  4. 组合多个语言查询的结果,提高召回率

实现模块

  • search/es_query_builder.py - ES 查询构建器(单层架构)
  • query/query_parser.py - 查询解析器(支持语言检测和翻译)
  • search/query_config.py - 查询配置(字段 boost、查询域等)

3. 数据导入流程

3.1 数据源

店匠标准表(Base配置使用):

  • shoplazza_product_spu - SPU级别商品数据
  • shoplazza_product_sku - SKU级别商品数据

其他客户表(tenant1等):

  • 使用各自的数据源表和扩展表

3.2 数据导入方式

数据源统一

  • 所有租户使用相同的MySQL表结构(店匠标准表)
  • 数据转换逻辑写死在转换器代码中
  • 索引结构硬编码,不依赖配置

数据导入流程(店匠通用)

脚本scripts/ingest_shoplazza.py

数据流程

  1. 数据加载

    • 从MySQL读取shoplazza_product_spu表(SPU数据)
    • 从MySQL读取shoplazza_product_sku表(SKU数据)
    • 从MySQL读取shoplazza_product_option表(选项定义)
  2. 数据转换indexer/spu_transformer.py):

    • spu_idtenant_id关联SPU和SKU数据
    • 多语言字段映射
      • MySQL的title → ES的title_zh(英文字段设为空)
      • 其他文本字段类似处理
    • 分类字段映射
      • 从SPU表的category_path解析多级类目(category1_name, category2_name, category3_name
      • 映射category_id, category_name, category_level
    • 规格字段构建specifications):
      • shoplazza_product_option表获取选项名称(name
      • 从SKU的option1/2/3字段获取选项值(value
      • 构建嵌套数组:[{"sku_id": "...", "name": "color", "value": "white"}, ...]
    • 选项名称映射
      • shoplazza_product_option表获取option1_name, option2_name, option3_name
    • SKU嵌套数组构建
      • 包含所有SKU字段(价格、库存、选项值、图片等)
    • 扁平化字段计算
      • min_price, max_price: 从所有SKU价格计算
      • sku_prices: 所有SKU价格数组
      • total_inventory: SKU库存总和
    • 注入tenant_id字段
  3. 索引创建

    • mappings/search_products.json加载ES mapping
    • 创建或更新search_products索引
  4. 批量入库

    • 批量写入ES(默认每批500条)
    • 错误处理和重试机制

命令行工具

python scripts/ingest_shoplazza.py \
    --db-host localhost \
    --db-port 3306 \
    --db-database saas \
    --db-username root \
    --db-password password \
    --tenant-id "1" \
    --config base \
    --es-host http://localhost:9200 \
    --recreate \
    --batch-size 500

其他客户数据导入

  • 使用各自的数据转换器(如indexer/data_transformer.py
  • 数据源映射逻辑写死在各自的转换器中
  • 共享search_products索引,通过tenant_id隔离

实现模块

  • indexer/spu_transformer.py - SPU数据转换器(Base配置)
  • indexer/data_transformer.py - 通用数据转换器(其他客户)
  • indexer/bulk_indexer.py - 批量索引器
  • scripts/ingest_shoplazza.py - 店匠数据导入脚本

4. QueryParser 实现

4.1 查询改写(Query Rewriting)

配置词典的key是query,value是改写后的查询表达式,比如。比如品牌词 改写为在brand|query OR name|query,类别词、标签词等都可以放进去。纠错、规范化、查询改写等 都可以通过这个词典来配置。 实现情况

配置方式

query_config.rewrite_dictionary 中配置查询改写规则:

query_config:
  enable_query_rewrite: true
  rewrite_dictionary:
    "芭比": "brand:芭比 OR name:芭比娃娃"
    "玩具": "category:玩具"
    "消防": "category:消防 OR name:消防"

功能特性

  • 精确匹配:查询完全匹配词典 key 时,替换为 value
  • 部分匹配:查询包含词典 key 时,替换该部分
  • 支持布尔表达式:value 可以是复杂的布尔表达式(AND, OR, 域查询等)

实现模块

  • query/query_rewriter.py - 查询改写器
  • query/query_parser.py - 查询解析器(集成改写功能)

4.2 翻译(Translation)

实现情况

配置方式

query_config:
  supported_languages:
    - "zh"
    - "en"
    - "ru"
  default_language: "zh"
  enable_translation: true
  translation_service: "deepl"
  translation_api_key: null  # 通过环境变量设置

功能特性

  1. 语言检测:自动检测查询语言
  2. 智能翻译
    • 如果查询是中文,翻译为英文、俄文
    • 如果查询是英文,翻译为中文、俄文
    • 如果查询是其他语言,翻译为所有支持的语言
  3. 域感知翻译
    • 如果域有 language_field_mapping,只翻译到映射中存在的语言
    • 避免不必要的翻译,提高效率
  4. 翻译缓存:缓存翻译结果,避免重复调用 API

工作流程

查询输入 → 语言检测 → 翻译 → 查询构建(filters and (text_recall or embedding_recall))

实现模块

  • query/language_detector.py - 语言检测器
  • query/translator.py - 翻译器(DeepL API)
  • query/query_parser.py - 查询解析器(集成翻译功能)

4.3 文本向量化(Text Embedding)

如果配置打开了text_embedding查询,并且query 包含了default域的查询,那么要把default域的查询词转向量,后面searcher会用这个向量参与查询。

实现情况

配置方式

query_config:
  enable_text_embedding: true

功能特性

  1. 条件生成
    • 仅当 enable_text_embedding=true 时生成向量
    • 仅对 default 域查询生成向量
  2. 向量模型:BGE-M3 模型(1024维向量)
  3. 用途:用于语义搜索(KNN 检索)

实现模块

  • embeddings/bge_encoder.py - BGE 文本编码器
  • query/query_parser.py - 查询解析器(集成向量生成)

5. Searcher 实现

参考opensearch,他们自己定义的一套索引结构配置、支持自定义的一套检索表达式、排序表达式,这是各个客户进行配置化的基础,包括索引结构配置、排序策略配置。 比如各种业务过滤策略 可以简单的通过表达式满足,比如brand|耐克 AND cate2|xxx。指定字段排序可以通过排序的表达式实现。

查询默认在default域,相也会对这个域的查询做一些相关性的重点优化,包括融合语义相关性、多语言相关性(可以基于配置 将查询翻译到指定语言并在对应的语言的字段进行查询)来弥补传统查询分析手段(比如查询改写 纠错 词权重等)的不足,也支持通过配置一些词表转为泛查询模式来优化相关性。

5.1 布尔表达式解析

实现情况

支持的运算符

  • AND:所有项必须匹配
  • OR:任意项匹配
  • RANK:排序增强(类似 OR 但影响排序)
  • ANDNOT:排除(第一项匹配,第二项不匹配)
  • ():括号分组

优先级(从高到低)

  1. () - 括号
  2. ANDNOT - 排除
  3. AND - 与
  4. OR - 或
  5. RANK - 排序

示例

laptop AND (gaming OR professional) ANDNOT cheap

实现模块

  • search/boolean_parser.py - 布尔表达式解析器
  • search/searcher.py - 搜索器(集成布尔解析)

5.2 多语言搜索

实现情况

工作原理

  1. 查询解析
    • 提取域(如 title:查询 → 域=title,查询=查询
    • 检测查询语言
    • 生成翻译
  2. 查询构建(简化架构):
    • 结构: filters AND (text_recall OR embedding_recall)
    • filters: 前端传递的过滤条件(永远起作用,放在 filter 中)
      • 普通字段过滤:{"category_name": "手机"}
      • 范围过滤:{"min_price": {"gte": 50, "lte": 200}}
      • Specifications嵌套过滤
      • 单个规格:{"specifications": {"name": "color", "value": "white"}}
      • 多个规格:{"specifications": [{"name": "color", "value": "white"}, {"name": "size", "value": "256GB"}]}
      • 过滤逻辑:不同维度(不同name)是AND关系,相同维度(相同name)的多个值是OR关系
      • 使用ES的nested查询实现
    • text_recall: 文本相关性召回
      • 同时搜索中英文字段(title_zh/en, brief_zh/en, description_zh/en, vendor_zh/en, category_path_zh/en, category_name_zh/en, tags
      • 使用 multi_match 查询,支持字段 boost
      • 中文字段使用中文分词器,英文字段使用英文分析器
    • embedding_recall: 向量召回(KNN)
      • 使用 title_embedding 字段进行 KNN 搜索
      • ES 自动与文本召回合并(OR逻辑)
    • function_score: 包装召回部分,支持提权字段(新鲜度、销量等)

查询结构示例

{
  "query": {
    "bool": {
      "must": [
        {
          "function_score": {
            "query": {
              "multi_match": {
                "query": "手机",
                "fields": [
                  "title_zh^3.0", "title_en^3.0",
                  "brief_zh^1.5", "brief_en^1.5",
                  ...
                ]
              }
            },
            "functions": [...]
          }
        }
      ],
      "filter": [
        {"term": {"tenant_id": "2"}},
        {"term": {"category_name": "手机"}}
      ]
    }
  },
  "knn": {
    "field": "title_embedding",
    "query_vector": [...],
    "k": 50,
    "boost": 0.2
  }
}

实现模块

  • search/es_query_builder.py - ES 查询构建器(单层架构,build_query 方法)
  • search/searcher.py - 搜索器(使用 ESQueryBuilder

5.3 相关性计算(Ranking)

实现情况

当前实现

公式bm25() + 0.2 * text_embedding_relevance()

  • bm25():BM25 文本相关性得分
    • 包括多语言打分
    • 内部通过配置翻译为多种语言
    • 分别到对应的字段搜索
    • 中文字段使用中文分词器,英文字段使用英文分词器
  • text_embedding_relevance():文本向量相关性得分(KNN 检索的打分)
    • 权重:0.2

配置方式

ranking:
  expression: "bm25() + 0.2*text_embedding_relevance()"
  description: "BM25 text relevance combined with semantic embedding similarity"

扩展性

  • 支持表达式配置(未来可扩展)
  • 支持自定义函数(如 timeliness(), field_value()

实现模块

  • search/ranking_engine.py - 排序引擎
  • search/searcher.py - 搜索器(集成排序功能)

6. 已完成功能总结

6.1 配置系统

  • ✅ 字段定义配置(类型、分析器、来源表/列)
  • ✅ 索引域配置(多域查询、多语言映射)
  • ✅ 查询配置(改写词典、翻译配置)
  • ✅ 排序配置(表达式配置)
  • ✅ 配置验证(字段存在性、类型检查、分析器匹配)

6.2 数据索引

  • ✅ 数据转换(字段映射、类型转换)
  • ✅ 向量生成(文本向量、图片向量)
  • ✅ 向量缓存(避免重复计算)
  • ✅ 批量索引(错误处理、重试机制)
  • ✅ ES mapping 自动生成

6.3 查询处理

  • ✅ 查询改写(词典配置)
  • ✅ 语言检测
  • ✅ 多语言翻译(DeepL API)
  • ✅ 文本向量化(BGE-M3)
  • ✅ 域提取(支持 domain:query 语法)

6.4 搜索功能

  • ✅ 布尔表达式解析(AND, OR, RANK, ANDNOT, 括号)
  • ✅ 多语言查询构建(同时搜索中英文字段)
  • ✅ 语义搜索(KNN 检索)
  • ✅ 相关性排序(BM25 + 向量相似度)
  • ✅ 结果聚合(Faceted Search)
  • ✅ Specifications嵌套过滤(单个和多个规格,按维度分组:不同维度AND,相同维度OR)
  • ✅ Specifications嵌套分面(所有规格名称和指定规格名称)
  • ✅ SKU筛选(按维度过滤,应用层实现)

6.5 API 服务

  • ✅ RESTful API(FastAPI)
  • ✅ 搜索接口(文本搜索、图片搜索)
  • ✅ 文档查询接口
  • ✅ 前端界面(HTML + JavaScript)
  • ✅ 租户隔离(tenant_id过滤)

6.6 索引结构(店匠通用)

  • ✅ SPU级别索引结构
  • ✅ 多语言字段支持(中英文)
  • ✅ 嵌套字段(skus, specifications, image_embedding)
  • ✅ 规格字段(specifications)支持过滤和分面
  • ✅ 扁平化字段(价格、库存等)用于过滤和排序
  • ✅ 统一索引(search_products)
  • ✅ 租户隔离(tenant_id)
  • ✅ 硬编码映射(mappings/search_products.json)
  • ✅ 硬编码查询配置(search/query_config.py)

7. 技术栈

  • 后端:Python 3.6+
  • 搜索引擎:Elasticsearch
  • 数据库:MySQL(Shoplazza)
  • 向量模型:BGE-M3(文本)、CN-CLIP(图片)
  • 翻译服务:DeepL API
  • API 框架:FastAPI
  • 前端:HTML + JavaScript

8. API响应格式

8.1 外部友好格式

API返回格式不包含ES内部字段(_id, _score, _source),使用外部友好的格式:

响应结构

{
  "results": [
    {
      "spu_id": "123",
      "title": "蓝牙耳机",
      "skus": [
        {
          "sku_id": "456",
          "price": 199.99,
          "sku": "SKU-123-1",
          "stock": 50
        }
      ],
      "relevance_score": 0.95
    }
  ],
  "total": 10,
  "facets": [...],
  "suggestions": [],
  "related_searches": []
}

主要变化

  • 结构化结果(SpuResultSkuResult
  • 嵌套skus数组
  • 无ES内部字段

8.2 租户隔离

所有API请求必须提供tenant_id

  • 请求头:X-Tenant-ID: 1
  • 或查询参数:?tenant_id=1

搜索时自动添加tenant_id过滤,确保数据隔离。

8.3 数据接口约定

统一的数据约定格式:所有API接口使用 Pydantic 模型进行数据验证和序列化。

8.3.1 数据流模式

系统采用统一的数据流模式,确保数据在各层之间的一致性:

数据流转路径

API Request (JSON)
    ↓
Pydantic 验证 → 结构化模型(RangeFilter, FacetConfig 等)
    ↓
Searcher(透传)
    ↓
ES Query Builder → model_dump() 转换为字典
    ↓
ES Query (字典)
    ↓
Elasticsearch

8.3.2 Facets 配置数据流

输入格式List[Union[str, FacetConfig]]

  • 简单模式:字符串列表(字段名),使用默认配置

    ["category1_name", "category2_name", "specifications"]
    
  • Specifications分面

    • 所有规格名称:"specifications" - 返回所有name及其value列表
    • 指定规格名称:"specifications.color" - 只返回指定name的value列表
  • 高级模式:FacetConfig 对象列表,支持自定义配置

    [
    {
      "field": "category1_name",
      "size": 15,
      "type": "terms"
    },
    {
      "field": "min_price",
      "type": "range",
      "ranges": [
        {"key": "0-50", "to": 50},
        {"key": "50-100", "from": 50, "to": 100}
      ]
    },
    "specifications.color"  // 指定规格名称的分面
    ]
    

数据流

  1. API 层:接收 List[Union[str, FacetConfig]]
  2. Searcher 层:透传,不做转换
  3. ES Query Builder:只接受 strFacetConfig,自动处理两种格式
    • 检测 "specifications""specifications.{name}" 格式
    • 构建对应的嵌套聚合查询
  4. 输出:转换为 ES 聚合查询(包括specifications嵌套聚合)
  5. Result Formatter:格式化ES聚合结果,处理specifications嵌套结构

8.3.3 Range Filters 数据流

输入格式Dict[str, RangeFilter]

RangeFilter 模型

class RangeFilter(BaseModel):
    gte: Optional[Union[float, str]]  # 大于等于
    gt: Optional[Union[float, str]]   # 大于
    lte: Optional[Union[float, str]]  # 小于等于
    lt: Optional[Union[float, str]]   # 小于

示例

{
  "min_price": {"gte": 50, "lte": 200},
  "create_time": {"gte": "2023-01-01T00:00:00Z"}
}

数据流

  1. API 层:接收 Dict[str, RangeFilter],Pydantic 自动验证
  2. Searcher 层:透传 Dict[str, RangeFilter]
  3. ES Query Builder:调用 range_filter.model_dump() 转换为字典
  4. 输出:ES range 查询(支持数值和日期)

特性

  • 自动验证:确保至少指定一个边界值(gte, gt, lte, lt)
  • 类型支持:支持数值(float)和日期时间字符串(ISO 格式)
  • 统一约定:所有范围过滤都使用 RangeFilter 模型

8.3.3.1 Specifications 过滤数据流

输入格式Dict[str, Union[Dict[str, str], List[Dict[str, str]]]]

单个规格过滤

{
  "specifications": {
    "name": "color",
    "value": "white"
  }
}

多个规格过滤(按维度分组)

{
  "specifications": [
    {"name": "color", "value": "white"},
    {"name": "size", "value": "256GB"}
  ]
}

数据流

  1. API 层:接收 filters 字典,检测 specifications
  2. Searcher 层:透传 filters 字典
  3. ES Query Builder:检测 specifications 键,构建ES nested 查询
    • 单个规格:构建单个 nested 查询
    • 多个规格:按 name 维度分组,相同维度内使用 should 组合(OR逻辑),不同维度之间使用 must 组合(AND逻辑)
  4. 输出:ES nested 查询(nested.path=specifications + bool.must=[term(name), term(value)]

8.3.4 响应 Facets 数据流

输出格式List[FacetResult]

FacetResult 模型

class FacetResult(BaseModel):
    field: str                    # 字段名
    label: str                    # 显示标签
    type: Literal["terms", "range"]  # 分面类型
    values: List[FacetValue]      # 分面值列表
    total_count: Optional[int]    # 总文档数

数据流

  1. ES Response:返回聚合结果(字典格式,包括specifications嵌套聚合)
  2. Result Formatter:格式化ES聚合结果
    • 处理普通terms聚合
    • 处理range聚合
    • 处理specifications嵌套聚合
      • 所有规格名称:解析 by_name 聚合结构
      • 指定规格名称:解析 filter_by_name 聚合结构
  3. Searcher 层:构建 List[FacetResult] 对象
  4. API 层:直接返回 List[FacetResult](Pydantic 自动序列化为 JSON)

优势

  • 类型安全:使用 Pydantic 模型确保数据结构一致性
  • 自动序列化:模型自动转换为 JSON,无需手动处理
  • 统一约定:所有响应都使用标准化的 Pydantic 模型

8.3.5 SKU筛选数据流

输入格式Optional[str]

支持的维度值

  • option1, option2, option3: 直接使用选项字段
  • 规格名称(如 color, size): 通过 option1_nameoption2_nameoption3_name 匹配

示例

{
  "query": "手机",
  "sku_filter_dimension": "color"
}

数据流

  1. API 层:接收 sku_filter_dimension 字符串参数
  2. Searcher 层:透传到 Result Formatter
  3. Result Formatter:在格式化结果时,按指定维度对SKU进行分组
    • 如果维度是 option1/2/3,直接使用对应的 option1_value/2/3 字段
    • 如果维度是规格名称,通过 option1_name/2/3 匹配找到对应的 option1_value/2/3
    • 每个分组选择第一个SKU返回
  4. 输出:过滤后的SKU列表(每个维度值一个SKU)

工作原理

  1. 系统从ES返回所有SKU(不改变ES查询,保持性能)
  2. 在结果格式化阶段,按指定维度对SKU进行分组
  3. 每个分组选择第一个SKU返回
  4. 如果维度不匹配或未找到,返回所有SKU(不进行过滤)

性能说明

  • 推荐方案: 在应用层过滤(当前实现)
    • ES查询简单,不需要nested查询和join
    • 只对返回的结果(通常10-20个SPU)进行过滤,数据量小
    • 实现简单,性能开销小
  • 不推荐: 在ES查询时过滤
    • 需要nested查询和join,性能开销大
    • 实现复杂
    • 只对返回的结果需要过滤,不需要在ES层面过滤

8.3.6 统一约定的好处

  1. 类型安全:使用 Pydantic 模型提供运行时类型检查和验证
  2. 代码一致性:所有层使用相同的数据模型,减少转换错误
  3. 自动文档:FastAPI 自动生成 API 文档(基于 Pydantic 模型)
  4. 易于维护:修改数据结构只需更新模型定义
  5. 数据验证:自动验证输入数据,减少错误处理代码

实现模块

  • api/models.py - 所有 Pydantic 模型定义(包括 SearchRequest, FacetConfig, RangeFilter 等)
  • api/result_formatter.py - 结果格式化器(ES 响应 → Pydantic 模型,包括specifications分面处理和SKU筛选)
  • search/es_query_builder.py - ES 查询构建器(Pydantic 模型 → ES 查询,包括specifications过滤和分面)

9. 索引结构文件

硬编码映射(店匠通用):mappings/search_products.json

查询配置(硬编码):search/query_config.py