# 搜索引擎通用化开发进度 ## 项目概述 对后端搜索技术 做通用化。 通用化的本质 是 对于各种业务数据、各种检索需求,都可以 用少量定制+配置化 来实现效果。 **通用化的本质**:对于各种业务数据、各种检索需求,都可以用少量定制+配置化来实现效果。 --- ## 1. 原始数据层的约定 ### 1.1 店匠主表 所有租户共用以下主表: - `shoplazza_product_sku` - SKU级别商品数据 - `shoplazza_product_spu` - SPU级别商品数据 ### 1.2 索引结构(SPU维度) **统一索引架构**: - 所有客户共享同一个Elasticsearch索引:`search_products` - 索引粒度:SPU级别(每个文档代表一个SPU) - 数据隔离:通过`tenant_id`字段实现租户隔离 - 嵌套结构:每个SPU文档包含嵌套的`skus`数组 **索引文档结构**: ```json { "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`) #### 多语言字段映射 支持将不同语言的查询路由到对应的字段: ```yaml 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_id`和`tenant_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条) - 错误处理和重试机制 **命令行工具**: ```bash 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` 中配置查询改写规则: ```yaml 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) **实现情况**: #### 配置方式 ```yaml 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会用这个向量参与查询。 **实现情况**: #### 配置方式 ```yaml 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"}}` - 多个规格(OR):`{"specifications": [{"name": "color", "value": "white"}, {"name": "size", "value": "256GB"}]}` - 使用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**: 包装召回部分,支持提权字段(新鲜度、销量等) #### 查询结构示例 ```json { "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 #### 配置方式 ```yaml 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嵌套过滤(单个和多个规格,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`),使用外部友好的格式: **响应结构**: ```json { "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": [] } ``` **主要变化**: - 结构化结果(`SpuResult`和`SkuResult`) - 嵌套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]]` - **简单模式**:字符串列表(字段名),使用默认配置 ```json ["category1_name", "category2_name", "specifications"] ``` - **Specifications分面**: - 所有规格名称:`"specifications"` - 返回所有name及其value列表 - 指定规格名称:`"specifications.color"` - 只返回指定name的value列表 - **高级模式**:FacetConfig 对象列表,支持自定义配置 ```json [ { "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:只接受 `str` 或 `FacetConfig`,自动处理两种格式 - 检测 `"specifications"` 或 `"specifications.{name}"` 格式 - 构建对应的嵌套聚合查询 4. 输出:转换为 ES 聚合查询(包括specifications嵌套聚合) 5. Result Formatter:格式化ES聚合结果,处理specifications嵌套结构 #### 8.3.3 Range Filters 数据流 **输入格式**:`Dict[str, RangeFilter]` **RangeFilter 模型**: ```python class RangeFilter(BaseModel): gte: Optional[Union[float, str]] # 大于等于 gt: Optional[Union[float, str]] # 大于 lte: Optional[Union[float, str]] # 小于等于 lt: Optional[Union[float, str]] # 小于 ``` **示例**: ```json { "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]]]]` **单个规格过滤**: ```json { "specifications": { "name": "color", "value": "white" } } ``` **多个规格过滤(OR逻辑)**: ```json { "specifications": [ {"name": "color", "value": "white"}, {"name": "size", "value": "256GB"} ] } ``` **数据流**: 1. API 层:接收 `filters` 字典,检测 `specifications` 键 2. Searcher 层:透传 `filters` 字典 3. ES Query Builder:检测 `specifications` 键,构建ES `nested` 查询 - 单个规格:构建单个 `nested` 查询 - 多个规格:构建多个 `nested` 查询,使用 `should` 组合(OR逻辑) 4. 输出:ES nested 查询(`nested.path=specifications` + `bool.must=[term(name), term(value)]`) #### 8.3.4 响应 Facets 数据流 **输出格式**:`List[FacetResult]` **FacetResult 模型**: ```python 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_name`、`option2_name`、`option3_name` 匹配 **示例**: ```json { "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` ---