# API响应格式优化与SPU索引重构 ## 概述 重构搜索系统以实现: 1. 外部接口友好的API响应格式(移除ES内部格式 `_hits`, `_source`, `_score`) 2. SPU维度的索引结构(包含嵌套variants数组) 3. 所有客户共用同一索引(使用tenant_id隔离) 4. 配置简化(完全移除MySQL相关配置,只保留ES搜索配置) 5. 创建base配置(店匠通用配置)和SPU数据导入流程 ## 核心设计原则 - **配置只关注ES搜索**:配置文件只包含ES字段定义、查询域、排序规则等搜索相关配置 - **数据灌入流程写死**:数据来源和转换逻辑由Pipeline层(脚本)决定,不在配置中体现 - **代码简洁无冗余**:不做向后兼容,直接删除不需要的代码 ## Phase 1: 配置系统重构 ### 1.1 创建BASE配置文件 **文件**: [`config/schema/base/config.yaml`](config/schema/base/config.yaml) (NEW) 创建店匠通用配置文件: - **不包含**:`mysql_config`, `main_table`, `extension_table`, `source_table`, `source_column` - **固定索引名称**:`search_products` - **SPU级别字段定义**: - `tenant_id` (KEYWORD, required) - `product_id` (对应SPU的id) - `title_zh`, `title_en` (多语言标题) - `description_zh`, `description_en` (多语言描述) - `min_price`, `max_price`, `compare_at_price` (扁平化价格) - `vendor`, `product_type`, `tags` 等 - **嵌套variants字段定义**: - `variants` (nested type) - variants包含:`variant_id`, `title`, `price`, `sku`, `stock`, `options` 等 ### 1.2 移除配置中的MySQL相关字段 **文件**: [`config/config_loader.py`](config/config_loader.py) 修改: - **删除** `TenantConfig` 中的 `mysql_config`, `main_table`, `extension_table` 字段定义 - **删除** `_parse_config` 中解析这些字段的代码 - **删除** `_parse_field_config` 中解析 `source_table`, `source_column` 的代码 - 保持其他配置解析逻辑不变 **文件**: [`config/field_types.py`](config/field_types.py) 修改: - **删除** `FieldConfig` 中的 `source_table`, `source_column` 字段定义 - 保持其他字段类型定义不变 ### 1.3 更新配置验证 **文件**: [`config/config_loader.py`](config/config_loader.py) 修改: - 添加 `tenant_id` 字段验证(必需字段) - 移除所有与MySQL/数据源相关的验证逻辑 ## Phase 2: 索引结构重构(SPU维度) ### 2.1 更新Mapping生成器 **文件**: [`indexer/mapping_generator.py`](indexer/mapping_generator.py) 修改: - 在 `_generate_mappings` 中: - 添加 `tenant_id` 字段(KEYWORD, required) - 支持嵌套 `variants` 字段(nested type) - 支持扁平化价格字段(`min_price`, `max_price`, `compare_at_price`) - 确保所有字段映射正确生成 ### 2.2 创建SPU数据转换器 **文件**: [`indexer/spu_transformer.py`](indexer/spu_transformer.py) (NEW) 创建SPU数据转换器(**不依赖配置中的source_table/source_column**): - **数据读取**:直接从MySQL读取 `shoplazza_product_spu` 和 `shoplazza_product_sku` 表(写死在代码中) - **数据聚合**:按 `spu_id` 和 `tenant_id` 关联,将SKU数据聚合为variants数组 - **字段映射**:将SPU和SKU表的字段映射到ES文档字段(写死在代码中) - **价格计算**:计算 `min_price`, `max_price`, `compare_at_price` - **向量生成**:支持文本和图片向量生成(复用现有encoder) - **文档生成**:生成SPU级别的ES文档,包含嵌套variants数组 - **tenant_id注入**:自动注入 `tenant_id` 字段 ### 2.3 创建店匠数据导入脚本 **文件**: [`scripts/ingest_shoplazza.py`](scripts/ingest_shoplazza.py) (NEW) 创建店匠数据导入脚本: - 连接MySQL数据库(连接信息通过参数或环境变量传入) - 读取 `shoplazza_product_spu` 和 `shoplazza_product_sku` 表 - 使用SPU转换器转换数据 - 批量导入到ES索引 `search_products` - 支持 `--tenant-id` 参数(必需) - 支持 `--recreate` 参数(重建索引) - 支持 `--batch-size` 参数 ## Phase 3: API响应格式重构 ### 3.1 更新响应模型 **文件**: [`api/models.py`](api/models.py) 修改: - **创建** `VariantResult` 模型: - `variant_id`, `title`, `price`, `sku`, `stock`, `options` 等 - **创建** `ProductResult` 模型: - `product_id`, `title`, `handle`, `description`, `vendor`, `product_type`, `tags` - `price`, `compare_at_price`, `currency`, `image_url`, `in_stock` - `variants` (List[VariantResult]) - `relevance_score` (float, 0-1) - **修改** `SearchResponse` 模型: - 将 `hits` 改为 `results` (List[ProductResult]) - 添加 `suggestions` (List[str]) - 添加 `related_searches` (List[str]) - 保持 `facets`, `total`, `took_ms` 等字段 ### 3.2 创建结果格式化器 **文件**: [`api/result_formatter.py`](api/result_formatter.py) (NEW) 创建结果格式化器: - **方法** `format_search_results(es_hits, max_score) -> List[ProductResult]`: - 将ES返回的 `_hits` 格式转换为 `ProductResult` 列表 - 提取SPU级别字段(从 `_source` 中提取) - 提取嵌套variants数组(从 `_source.variants` 中提取) - 计算 `relevance_score`(从 `_score` 归一化到0-1,基于max_score) - 处理缺失字段(提供默认值) - **方法** `format_facets(es_aggregations, facet_configs) -> List[FacetResult]`: - 格式化facets结果(保持现有逻辑) - **方法** `generate_suggestions(query, results) -> List[str]`: - 生成搜索建议(暂时返回空数组) - **方法** `generate_related_searches(query, results) -> List[str]`: - 生成相关搜索(暂时返回空数组) ### 3.3 更新搜索器 **文件**: [`search/searcher.py`](search/searcher.py) 修改: - **添加** `tenant_id` 参数到 `search` 方法(必需) - **修改** 查询构建:在filter中添加 `tenant_id` 过滤(必需) - **修改** 结果处理: - 使用 `ResultFormatter` 格式化结果 - 返回 `results` 而不是 `hits` - 移除ES内部格式字段(`_id`, `_score`, `_source`) - **修改** `SearchResult` 类: - 将 `hits` 改为 `results` (List[ProductResult]) - 添加 `suggestions` 和 `related_searches` 字段 ### 3.4 更新API路由 **文件**: [`api/routes/search.py`](api/routes/search.py) 修改: - **添加** `tenant_id` 参数获取: - 优先从请求头 `X-Tenant-ID` 获取 - 其次从查询参数 `tenant_id` 获取 - 如果都没有,返回400错误 - **修改** 搜索调用: - 传递 `tenant_id` 给 `searcher.search()` - **修改** 响应构建: - 使用格式化后的 `results` - 添加 `suggestions` 和 `related_searches`(暂时返回空数组) ## Phase 4: DataTransformer重构 ### 4.1 重构DataTransformer(向后兼容,用于tenant1) **文件**: [`indexer/data_transformer.py`](indexer/data_transformer.py) 修改: - **保持** 现有逻辑(用于tenant1等旧配置) - **添加** 检查:如果 `field.source_column` 不存在,跳过该字段(向后兼容) - **注意**:base配置不使用DataTransformer,使用SPU转换器 ## Phase 5: 测试数据生成 ### 5.1 创建测试数据生成脚本 **文件**: [`scripts/generate_test_data.py`](scripts/generate_test_data.py) (NEW) 创建测试数据生成脚本: - 生成100条SPU测试数据(符合 `shoplazza_product_spu` 表结构) - 为每个SPU生成1-5个SKU变体(符合 `shoplazza_product_sku` 表结构) - 包含中文和英文标题 - 包含价格、库存、图片等字段 - 输出为MySQL INSERT语句或CSV文件 - 设置 `tenant_id` 为指定值 ### 5.2 创建数据导入脚本 **文件**: [`scripts/import_test_data.py`](scripts/import_test_data.py) (NEW) 创建数据导入脚本: - 连接MySQL数据库 - 导入测试数据到 `shoplazza_product_spu` 和 `shoplazza_product_sku` 表 - 设置 `tenant_id` 为base客户的ID(如 "base" 或 "1") - 验证数据导入结果 ## Phase 6: 测试脚本和文档 ### 6.1 创建测试脚本 **文件**: [`scripts/test_base.py`](scripts/test_base.py) (NEW) 创建base配置测试脚本: - 测试数据导入(使用ingest_shoplazza.py) - 测试搜索API(验证tenant_id过滤) - 测试多语言搜索 - 测试facets聚合 - **验证响应格式**: - 确认返回 `results` 而不是 `hits` - 确认每个result包含 `product_id`, `title`, `variants`, `relevance_score` - 确认variants数组格式正确 - 确认没有ES内部格式字段(`_id`, `_score`, `_source`) ### 6.2 创建说明文档 **文件**: [`docs/BASE_CONFIG_GUIDE.md`](docs/BASE_CONFIG_GUIDE.md) (NEW) 创建base配置测试指南: - 数据导入步骤 - 配置说明(强调不包含MySQL配置) - API测试示例(包含tenant_id参数) - 响应格式说明 - 常见问题解答 ## Phase 7: 更新设计文档 ### 7.1 更新设计文档 **文件**: [`设计文档.md`](设计文档.md) 修改: - 更新索引结构说明(SPU维度,所有客户共用 `search_products` 索引) - 更新配置说明(完全移除MySQL相关配置,只保留ES搜索配置) - 更新API响应格式说明(results格式,非hits格式) - 更新数据导入流程说明(Pipeline层决定数据源,配置不包含数据源信息) - 添加base配置说明 - 添加tenant_id隔离说明 ## 关键修改点总结 1. **配置系统**: - 完全移除 `mysql_config`, `main_table`, `extension_table`, `source_table`, `source_column` - 配置只包含ES搜索相关配置 - 代码简洁,无冗余,无向后兼容逻辑 2. **数据转换**: - SPU转换器直接从MySQL读取,不依赖配置 - 数据映射逻辑写死在代码中 - 支持嵌套variants数组 3. **索引结构**: - SPU级别索引 - 嵌套variants字段 - tenant_id字段(必需) 4. **API响应**: - 从 `hits` 改为 `results` - 从 `_id`, `_score`, `_source` 改为 `product_id`, `relevance_score`, 结构化字段 - 包含 `variants` 数组 5. **租户隔离**: - 所有客户共用 `search_products` 索引 - 使用 `tenant_id` 字段过滤 - API必须提供 `tenant_id` 参数 ### To-dos - [ ] Create ResponseTransformer to convert ES hits to Shoplazza format (results, facets, suggestions, related_searches) - [ ] Update API models: add VariantOption, ProductVariant, ProductResult, update SearchResponse with new format - [ ] Update search route to use ResponseTransformer and return Shoplazza format - [ ] Create script to generate 100 SPU+SKU test records for tenant2 in Shoplazza tables - [ ] Create tenant2 config.yaml with search-only fields (no pipeline details) - [ ] Create or update SPUDataTransformer to join SPU+SKU and create nested variants structure - [ ] Create tenant2 ingestion script that loads from MySQL and uses SPU transformer - [ ] Create test script and documentation for tenant2 setup and testing - [ ] Update design document: SPU-level indexing, unified index, config separation, pipeline decisions