Commit f7d3cf70b2b2a3d50e8e4c3ce5aceaad157d9daa
1 parent
f0d020c3
更新文档
1. 搜索API对接指南.md 在“精确匹配过滤器”部分添加了 specifications 嵌套过滤说明 支持单个规格过滤和多个规格过滤(OR 逻辑) 在“分面配置”部分完善了 specifications 分面说明 添加了两种分面模式:所有规格名称和指定规格名称 在“常见场景示例”部分添加了场景5-8,包含规格过滤和分面的完整示例 2. 搜索API速查表.md 在“精确匹配过滤”部分添加了 specifications 过滤的快速参考 在“分面搜索”部分添加了 specifications 分面的快速参考 更新了完整示例,包含 specifications 的使用 3. Search-API-Examples.md 在“过滤器使用”部分添加了示例4-6,展示 specifications 过滤 在“分面搜索”部分添加了示例2-3,展示 specifications 分面 更新了 Python 和 JavaScript 完整示例,包含 specifications 的使用 在“常见使用场景”部分添加了场景2.1,展示带规格过滤的搜索结果页 4. 索引字段说明v2.md 更新了 specifications 字段的查询示例,包含 API 格式和 ES 查询结构 添加了两种分面模式的说明和示例 更新了“分面字段”说明,明确支持指定规格名称的分面
Showing
10 changed files
with
1441 additions
and
614 deletions
Show diff stats
README.md
| ... | ... | @@ -54,11 +54,11 @@ curl -X POST http://localhost:6002/search/ \ |
| 54 | 54 | |------|----------|----------| |
| 55 | 55 | | `环境配置说明.md` | 系统要求、Conda/依赖、外部服务账号、常用端口 | 首次部署、环境核对 | |
| 56 | 56 | | `Usage-Guide.md` | 环境准备、服务启动、配置、日志、验证手册 | 日常运维、调试 | |
| 57 | -| `基础配置指南.md` | 租户字段、索引域、排序表达式配置流程 | 新租户开通、配置变更 | | |
| 57 | +| `基础配置指南.md` | 统一硬编码配置说明、索引结构、查询配置 | 了解系统配置、修改配置 | | |
| 58 | 58 | | `测试数据指南.md` | 两个租户的模拟/CSV 数据构造 & MySQL→ES 流程 | 数据准备、联调 | |
| 59 | 59 | | `测试Pipeline说明.md` | 测试流水线、CI 脚本、上下文说明 | 自动化测试、追踪流水线 | |
| 60 | 60 | | `系统设计文档.md` | 架构、配置系统、索引/查询/排序模块细节 | 研发/扩展功能 | |
| 61 | -| `索引字段说明.md` | `search_products` 字段、类型、来源、嵌套结构 | 新增字段、数据对齐 | | |
| 61 | +| `索引字段说明v2.md` | `search_products` 字段、类型、来源、嵌套结构 | 新增字段、数据对齐 | | |
| 62 | 62 | | `搜索API对接指南.md` | REST API(文本/图片/管理)详解、示例、响应格式 | API 使用、测试 | |
| 63 | 63 | | `搜索API速查表.md` | 常用请求体、过滤器、分面速查表 | 支持团队快速查阅 | |
| 64 | 64 | | `Search-API-Examples.md` | Python/JS/cURL 端到端示例 | 客户工程、SDK 参考 | |
| ... | ... | @@ -82,9 +82,11 @@ curl -X POST http://localhost:6002/search/ \ |
| 82 | 82 | - API、分页、过滤、Facet、KNN 等:`搜索API对接指南.md` |
| 83 | 83 | - 对接案例、示例与错误码:`搜索API对接指南.md`、`Search-API-Examples.md` |
| 84 | 84 | |
| 85 | -- **配置驱动能力** | |
| 86 | - - `config/schema/{tenant_id}/config.yaml`:字段定义、索引域、排序表达式、SPU 聚合 | |
| 87 | - - 详解与设计理念:`设计文档.md`、`INDEX_FIELDS_DOCUMENTATION.md` | |
| 85 | +- **统一配置** | |
| 86 | + - 所有租户共享统一的索引结构和查询配置(硬编码) | |
| 87 | + - 索引 mapping: `mappings/search_products.json` | |
| 88 | + - 查询配置: `search/query_config.py` | |
| 89 | + - 详解:`基础配置指南.md`、`索引字段说明v2.md` | |
| 88 | 90 | |
| 89 | 91 | ## 仓库结构(概览) |
| 90 | 92 | ... | ... |
api/models.py
| ... | ... | @@ -75,15 +75,21 @@ class SearchRequest(BaseModel): |
| 75 | 75 | ) |
| 76 | 76 | |
| 77 | 77 | # 过滤器 - 精确匹配和多值匹配 |
| 78 | - filters: Optional[Dict[str, Union[str, int, bool, List[Union[str, int]]]]] = Field( | |
| 78 | + filters: Optional[Dict[str, Union[str, int, bool, List[Union[str, int]], Dict[str, Any], List[Dict[str, Any]]]]] = Field( | |
| 79 | 79 | None, |
| 80 | - description="精确匹配过滤器。单值表示精确匹配,数组表示 OR 匹配(匹配任意一个值)", | |
| 80 | + description="精确匹配过滤器。单值表示精确匹配,数组表示 OR 匹配(匹配任意一个值)。支持 specifications 嵌套过滤:{\"specifications\": {\"name\": \"color\", \"value\": \"green\"}} 或 [{\"name\": \"color\", \"value\": \"green\"}, ...]", | |
| 81 | 81 | json_schema_extra={ |
| 82 | 82 | "examples": [ |
| 83 | 83 | { |
| 84 | - "category.keyword": ["玩具", "益智玩具"], | |
| 85 | - "vendor.keyword": "乐高", | |
| 86 | - "in_stock": True | |
| 84 | + "category_name": ["手机", "电子产品"], | |
| 85 | + "vendor_zh.keyword": "奇乐", | |
| 86 | + "specifications": {"name": "颜色", "value": "白色"} | |
| 87 | + }, | |
| 88 | + { | |
| 89 | + "specifications": [ | |
| 90 | + {"name": "颜色", "value": "白色"}, | |
| 91 | + {"name": "尺寸", "value": "256GB"} | |
| 92 | + ] | |
| 87 | 93 | } |
| 88 | 94 | ] |
| 89 | 95 | } |
| ... | ... | @@ -110,22 +116,25 @@ class SearchRequest(BaseModel): |
| 110 | 116 | # 分面搜索 - 简化接口 |
| 111 | 117 | facets: Optional[List[Union[str, FacetConfig]]] = Field( |
| 112 | 118 | None, |
| 113 | - description="分面配置。可以是字段名列表(使用默认配置)或详细的分面配置对象", | |
| 119 | + description="分面配置。可以是字段名列表(使用默认配置)或详细的分面配置对象。支持 specifications 分面:\"specifications\"(所有name)或 \"specifications.color\"(指定name)", | |
| 114 | 120 | json_schema_extra={ |
| 115 | 121 | "examples": [ |
| 116 | 122 | # 简单模式:只指定字段名,使用默认配置 |
| 117 | - ["category.keyword", "vendor.keyword"], | |
| 123 | + ["category1_name", "category2_name", "specifications"], | |
| 124 | + # 指定specifications的某个name | |
| 125 | + ["specifications.颜色", "specifications.尺寸"], | |
| 118 | 126 | # 高级模式:详细配置 |
| 119 | 127 | [ |
| 120 | - {"field": "category.keyword", "size": 15}, | |
| 128 | + {"field": "category1_name", "size": 15}, | |
| 121 | 129 | { |
| 122 | - "field": "price", | |
| 130 | + "field": "min_price", | |
| 123 | 131 | "type": "range", |
| 124 | 132 | "ranges": [ |
| 125 | 133 | {"key": "0-50", "to": 50}, |
| 126 | 134 | {"key": "50-100", "from": 50, "to": 100} |
| 127 | 135 | ] |
| 128 | - } | |
| 136 | + }, | |
| 137 | + "specifications" # 所有specifications name的分面 | |
| 129 | 138 | ] |
| 130 | 139 | ] |
| 131 | 140 | } | ... | ... |
api/result_formatter.py
| ... | ... | @@ -143,7 +143,7 @@ class ResultFormatter: |
| 143 | 143 | for field_name, agg_data in es_aggregations.items(): |
| 144 | 144 | display_field = field_name[:-6] if field_name.endswith("_facet") else field_name |
| 145 | 145 | |
| 146 | - # 处理specifications嵌套分面 | |
| 146 | + # 处理specifications嵌套分面(所有name) | |
| 147 | 147 | if field_name == "specifications_facet" and 'by_name' in agg_data: |
| 148 | 148 | # specifications嵌套聚合:按name分组,每个name下有value_counts |
| 149 | 149 | by_name_agg = agg_data['by_name'] |
| ... | ... | @@ -174,6 +174,35 @@ class ResultFormatter: |
| 174 | 174 | facets.append(facet) |
| 175 | 175 | continue |
| 176 | 176 | |
| 177 | + # 处理specifications嵌套分面(指定name) | |
| 178 | + if field_name.startswith("specifications_") and field_name.endswith("_facet") and 'filter_by_name' in agg_data: | |
| 179 | + # 提取name(从 "specifications_颜色_facet" 提取 "颜色") | |
| 180 | + name = field_name[len("specifications_"):-len("_facet")] | |
| 181 | + filter_by_name_agg = agg_data.get('filter_by_name', {}) | |
| 182 | + value_counts = filter_by_name_agg.get('value_counts', {}) | |
| 183 | + | |
| 184 | + values = [] | |
| 185 | + if 'buckets' in value_counts: | |
| 186 | + for value_bucket in value_counts['buckets']: | |
| 187 | + value = FacetValue( | |
| 188 | + value=value_bucket['key'], | |
| 189 | + label=str(value_bucket['key']), | |
| 190 | + count=value_bucket['doc_count'], | |
| 191 | + selected=False | |
| 192 | + ) | |
| 193 | + values.append(value) | |
| 194 | + | |
| 195 | + # 创建分面结果 | |
| 196 | + facet = FacetResult( | |
| 197 | + field=f"specifications.{name}", | |
| 198 | + label=str(name), | |
| 199 | + type="terms", | |
| 200 | + values=values, | |
| 201 | + total_count=filter_by_name_agg.get('doc_count', 0) | |
| 202 | + ) | |
| 203 | + facets.append(facet) | |
| 204 | + continue | |
| 205 | + | |
| 177 | 206 | # Handle terms aggregation |
| 178 | 207 | if 'buckets' in agg_data: |
| 179 | 208 | values = [] | ... | ... |
docs/Search-API-Examples.md
| ... | ... | @@ -23,6 +23,7 @@ |
| 23 | 23 | ```bash |
| 24 | 24 | curl -X POST "http://localhost:6002/search/" \ |
| 25 | 25 | -H "Content-Type: application/json" \ |
| 26 | + -H "X-Tenant-ID: 2" \ | |
| 26 | 27 | -d '{ |
| 27 | 28 | "query": "芭比娃娃" |
| 28 | 29 | }' |
| ... | ... | @@ -48,8 +49,10 @@ curl -X POST "http://localhost:6002/search/" \ |
| 48 | 49 | ```bash |
| 49 | 50 | curl -X POST "http://localhost:6002/search/" \ |
| 50 | 51 | -H "Content-Type: application/json" \ |
| 52 | + -H "X-Tenant-ID: 2" \ | |
| 51 | 53 | -d '{ |
| 52 | - "query": "玩具", | |
| 54 | + "query": "手机", | |
| 55 | + "language": "zh", | |
| 53 | 56 | "size": 50 |
| 54 | 57 | }' |
| 55 | 58 | ``` |
| ... | ... | @@ -60,8 +63,10 @@ curl -X POST "http://localhost:6002/search/" \ |
| 60 | 63 | # 第1页(0-19) |
| 61 | 64 | curl -X POST "http://localhost:6002/search/" \ |
| 62 | 65 | -H "Content-Type: application/json" \ |
| 66 | + -H "X-Tenant-ID: 2" \ | |
| 63 | 67 | -d '{ |
| 64 | - "query": "玩具", | |
| 68 | + "query": "手机", | |
| 69 | + "language": "zh", | |
| 65 | 70 | "size": 20, |
| 66 | 71 | "from": 0 |
| 67 | 72 | }' |
| ... | ... | @@ -69,8 +74,10 @@ curl -X POST "http://localhost:6002/search/" \ |
| 69 | 74 | # 第2页(20-39) |
| 70 | 75 | curl -X POST "http://localhost:6002/search/" \ |
| 71 | 76 | -H "Content-Type: application/json" \ |
| 77 | + -H "X-Tenant-ID: 2" \ | |
| 72 | 78 | -d '{ |
| 73 | - "query": "玩具", | |
| 79 | + "query": "手机", | |
| 80 | + "language": "zh", | |
| 74 | 81 | "size": 20, |
| 75 | 82 | "from": 20 |
| 76 | 83 | }' |
| ... | ... | @@ -87,10 +94,12 @@ curl -X POST "http://localhost:6002/search/" \ |
| 87 | 94 | ```bash |
| 88 | 95 | curl -X POST "http://localhost:6002/search/" \ |
| 89 | 96 | -H "Content-Type: application/json" \ |
| 97 | + -H "X-Tenant-ID: 2" \ | |
| 90 | 98 | -d '{ |
| 91 | - "query": "玩具", | |
| 99 | + "query": "手机", | |
| 100 | + "language": "zh", | |
| 92 | 101 | "filters": { |
| 93 | - "category.keyword": "玩具" | |
| 102 | + "category_name": "手机" | |
| 94 | 103 | } |
| 95 | 104 | }' |
| 96 | 105 | ``` |
| ... | ... | @@ -100,10 +109,12 @@ curl -X POST "http://localhost:6002/search/" \ |
| 100 | 109 | ```bash |
| 101 | 110 | curl -X POST "http://localhost:6002/search/" \ |
| 102 | 111 | -H "Content-Type: application/json" \ |
| 112 | + -H "X-Tenant-ID: 2" \ | |
| 103 | 113 | -d '{ |
| 104 | - "query": "娃娃", | |
| 114 | + "query": "手机", | |
| 115 | + "language": "zh", | |
| 105 | 116 | "filters": { |
| 106 | - "category.keyword": ["玩具", "益智玩具", "儿童玩具"] | |
| 117 | + "category_name": ["手机", "电子产品"] | |
| 107 | 118 | } |
| 108 | 119 | }' |
| 109 | 120 | ``` |
| ... | ... | @@ -115,16 +126,79 @@ curl -X POST "http://localhost:6002/search/" \ |
| 115 | 126 | ```bash |
| 116 | 127 | curl -X POST "http://localhost:6002/search/" \ |
| 117 | 128 | -H "Content-Type: application/json" \ |
| 129 | + -H "X-Tenant-ID: 2" \ | |
| 118 | 130 | -d '{ |
| 119 | - "query": "娃娃", | |
| 131 | + "query": "手机", | |
| 132 | + "language": "zh", | |
| 120 | 133 | "filters": { |
| 121 | - "category.keyword": "玩具", | |
| 122 | - "vendor.keyword": "美泰" | |
| 134 | + "category_name": "手机", | |
| 135 | + "vendor_zh.keyword": "奇乐" | |
| 123 | 136 | } |
| 124 | 137 | }' |
| 125 | 138 | ``` |
| 126 | 139 | |
| 127 | -说明:必须同时满足"类目=玩具" **并且** "品牌=美泰"。 | |
| 140 | +说明:必须同时满足"类目=手机" **并且** "品牌=奇乐"。 | |
| 141 | + | |
| 142 | +#### 示例 4:Specifications 嵌套过滤(单个规格) | |
| 143 | + | |
| 144 | +```bash | |
| 145 | +curl -X POST "http://localhost:6002/search/" \ | |
| 146 | + -H "Content-Type: application/json" \ | |
| 147 | + -H "X-Tenant-ID: 2" \ | |
| 148 | + -d '{ | |
| 149 | + "query": "手机", | |
| 150 | + "language": "zh", | |
| 151 | + "filters": { | |
| 152 | + "specifications": { | |
| 153 | + "name": "color", | |
| 154 | + "value": "white" | |
| 155 | + } | |
| 156 | + } | |
| 157 | + }' | |
| 158 | +``` | |
| 159 | + | |
| 160 | +说明:查询规格名称为"color"且值为"white"的商品。 | |
| 161 | + | |
| 162 | +#### 示例 5:Specifications 嵌套过滤(多个规格,OR逻辑) | |
| 163 | + | |
| 164 | +```bash | |
| 165 | +curl -X POST "http://localhost:6002/search/" \ | |
| 166 | + -H "Content-Type: application/json" \ | |
| 167 | + -H "X-Tenant-ID: 2" \ | |
| 168 | + -d '{ | |
| 169 | + "query": "手机", | |
| 170 | + "language": "zh", | |
| 171 | + "filters": { | |
| 172 | + "specifications": [ | |
| 173 | + {"name": "color", "value": "white"}, | |
| 174 | + {"name": "size", "value": "256GB"} | |
| 175 | + ] | |
| 176 | + } | |
| 177 | + }' | |
| 178 | +``` | |
| 179 | + | |
| 180 | +说明:查询满足任意一个规格的商品(color=white **或** size=256GB)。 | |
| 181 | + | |
| 182 | +#### 示例 6:组合过滤(包含 specifications) | |
| 183 | + | |
| 184 | +```bash | |
| 185 | +curl -X POST "http://localhost:6002/search/" \ | |
| 186 | + -H "Content-Type: application/json" \ | |
| 187 | + -H "X-Tenant-ID: 2" \ | |
| 188 | + -d '{ | |
| 189 | + "query": "手机", | |
| 190 | + "language": "zh", | |
| 191 | + "filters": { | |
| 192 | + "category_name": "手机", | |
| 193 | + "specifications": { | |
| 194 | + "name": "color", | |
| 195 | + "value": "white" | |
| 196 | + } | |
| 197 | + } | |
| 198 | + }' | |
| 199 | +``` | |
| 200 | + | |
| 201 | +说明:同时满足类目=手机 **并且** color=white。 | |
| 128 | 202 | |
| 129 | 203 | ### 范围过滤器 |
| 130 | 204 | |
| ... | ... | @@ -133,10 +207,12 @@ curl -X POST "http://localhost:6002/search/" \ |
| 133 | 207 | ```bash |
| 134 | 208 | curl -X POST "http://localhost:6002/search/" \ |
| 135 | 209 | -H "Content-Type: application/json" \ |
| 210 | + -H "X-Tenant-ID: 2" \ | |
| 136 | 211 | -d '{ |
| 137 | - "query": "玩具", | |
| 212 | + "query": "手机", | |
| 213 | + "language": "zh", | |
| 138 | 214 | "range_filters": { |
| 139 | - "price": { | |
| 215 | + "min_price": { | |
| 140 | 216 | "gte": 50, |
| 141 | 217 | "lte": 200 |
| 142 | 218 | } |
| ... | ... | @@ -151,10 +227,12 @@ curl -X POST "http://localhost:6002/search/" \ |
| 151 | 227 | ```bash |
| 152 | 228 | curl -X POST "http://localhost:6002/search/" \ |
| 153 | 229 | -H "Content-Type: application/json" \ |
| 230 | + -H "X-Tenant-ID: 2" \ | |
| 154 | 231 | -d '{ |
| 155 | - "query": "玩具", | |
| 232 | + "query": "手机", | |
| 233 | + "language": "zh", | |
| 156 | 234 | "range_filters": { |
| 157 | - "price": { | |
| 235 | + "min_price": { | |
| 158 | 236 | "gte": 100 |
| 159 | 237 | } |
| 160 | 238 | } |
| ... | ... | @@ -168,10 +246,12 @@ curl -X POST "http://localhost:6002/search/" \ |
| 168 | 246 | ```bash |
| 169 | 247 | curl -X POST "http://localhost:6002/search/" \ |
| 170 | 248 | -H "Content-Type: application/json" \ |
| 249 | + -H "X-Tenant-ID: 2" \ | |
| 171 | 250 | -d '{ |
| 172 | - "query": "玩具", | |
| 251 | + "query": "手机", | |
| 252 | + "language": "zh", | |
| 173 | 253 | "range_filters": { |
| 174 | - "price": { | |
| 254 | + "min_price": { | |
| 175 | 255 | "lt": 50 |
| 176 | 256 | } |
| 177 | 257 | } |
| ... | ... | @@ -185,10 +265,12 @@ curl -X POST "http://localhost:6002/search/" \ |
| 185 | 265 | ```bash |
| 186 | 266 | curl -X POST "http://localhost:6002/search/" \ |
| 187 | 267 | -H "Content-Type: application/json" \ |
| 268 | + -H "X-Tenant-ID: 2" \ | |
| 188 | 269 | -d '{ |
| 189 | - "query": "玩具", | |
| 270 | + "query": "手机", | |
| 271 | + "language": "zh", | |
| 190 | 272 | "range_filters": { |
| 191 | - "price": { | |
| 273 | + "min_price": { | |
| 192 | 274 | "gte": 50, |
| 193 | 275 | "lte": 200 |
| 194 | 276 | }, |
| ... | ... | @@ -206,14 +288,16 @@ curl -X POST "http://localhost:6002/search/" \ |
| 206 | 288 | ```bash |
| 207 | 289 | curl -X POST "http://localhost:6002/search/" \ |
| 208 | 290 | -H "Content-Type: application/json" \ |
| 291 | + -H "X-Tenant-ID: 2" \ | |
| 209 | 292 | -d '{ |
| 210 | - "query": "玩具", | |
| 293 | + "query": "手机", | |
| 294 | + "language": "zh", | |
| 211 | 295 | "filters": { |
| 212 | - "category.keyword": ["玩具", "益智玩具"], | |
| 213 | - "vendor.keyword": "乐高" | |
| 296 | + "category_name": ["手机", "电子产品"], | |
| 297 | + "vendor_zh.keyword": "品牌A" | |
| 214 | 298 | }, |
| 215 | 299 | "range_filters": { |
| 216 | - "price": { | |
| 300 | + "min_price": { | |
| 217 | 301 | "gte": 50, |
| 218 | 302 | "lte": 500 |
| 219 | 303 | } |
| ... | ... | @@ -234,41 +318,82 @@ curl -X POST "http://localhost:6002/search/" \ |
| 234 | 318 | ```bash |
| 235 | 319 | curl -X POST "http://localhost:6002/search/" \ |
| 236 | 320 | -H "Content-Type: application/json" \ |
| 321 | + -H "X-Tenant-ID: 2" \ | |
| 237 | 322 | -d '{ |
| 238 | - "query": "玩具", | |
| 323 | + "query": "手机", | |
| 324 | + "language": "zh", | |
| 239 | 325 | "size": 20, |
| 240 | - "facets": ["category.keyword", "vendor.keyword"] | |
| 326 | + "facets": ["category1_name", "category2_name", "specifications"] | |
| 241 | 327 | }' |
| 242 | 328 | ``` |
| 243 | 329 | |
| 244 | 330 | **响应**: |
| 245 | 331 | ```json |
| 246 | 332 | { |
| 247 | - "hits": [...], | |
| 333 | + "results": [...], | |
| 248 | 334 | "total": 118, |
| 249 | 335 | "facets": [ |
| 250 | 336 | { |
| 251 | - "field": "category.keyword", | |
| 252 | - "label": "category.keyword", | |
| 337 | + "field": "category1_name", | |
| 338 | + "label": "category1_name", | |
| 253 | 339 | "type": "terms", |
| 254 | 340 | "values": [ |
| 255 | - {"value": "玩具", "count": 85, "selected": false}, | |
| 256 | - {"value": "益智玩具", "count": 33, "selected": false} | |
| 341 | + {"value": "手机", "count": 85, "selected": false}, | |
| 342 | + {"value": "电子产品", "count": 33, "selected": false} | |
| 257 | 343 | ] |
| 258 | 344 | }, |
| 259 | 345 | { |
| 260 | - "field": "vendor.keyword", | |
| 261 | - "label": "vendor.keyword", | |
| 346 | + "field": "specifications.color", | |
| 347 | + "label": "color", | |
| 262 | 348 | "type": "terms", |
| 263 | 349 | "values": [ |
| 264 | - {"value": "乐高", "count": 42, "selected": false}, | |
| 265 | - {"value": "美泰", "count": 28, "selected": false} | |
| 350 | + {"value": "white", "count": 50, "selected": false}, | |
| 351 | + {"value": "black", "count": 30, "selected": false} | |
| 352 | + ] | |
| 353 | + }, | |
| 354 | + { | |
| 355 | + "field": "specifications.size", | |
| 356 | + "label": "size", | |
| 357 | + "type": "terms", | |
| 358 | + "values": [ | |
| 359 | + {"value": "256GB", "count": 40, "selected": false}, | |
| 360 | + {"value": "512GB", "count": 20, "selected": false} | |
| 266 | 361 | ] |
| 267 | 362 | } |
| 268 | 363 | ] |
| 269 | 364 | } |
| 270 | 365 | ``` |
| 271 | 366 | |
| 367 | +#### 示例 2:Specifications 分面(所有规格名称) | |
| 368 | + | |
| 369 | +```bash | |
| 370 | +curl -X POST "http://localhost:6002/search/" \ | |
| 371 | + -H "Content-Type: application/json" \ | |
| 372 | + -H "X-Tenant-ID: 2" \ | |
| 373 | + -d '{ | |
| 374 | + "query": "手机", | |
| 375 | + "language": "zh", | |
| 376 | + "facets": ["specifications"] | |
| 377 | + }' | |
| 378 | +``` | |
| 379 | + | |
| 380 | +说明:返回所有规格名称(name)及其对应的值(value)列表。 | |
| 381 | + | |
| 382 | +#### 示例 3:Specifications 分面(指定规格名称) | |
| 383 | + | |
| 384 | +```bash | |
| 385 | +curl -X POST "http://localhost:6002/search/" \ | |
| 386 | + -H "Content-Type: application/json" \ | |
| 387 | + -H "X-Tenant-ID: 2" \ | |
| 388 | + -d '{ | |
| 389 | + "query": "手机", | |
| 390 | + "language": "zh", | |
| 391 | + "facets": ["specifications.color", "specifications.size"] | |
| 392 | + }' | |
| 393 | +``` | |
| 394 | + | |
| 395 | +说明:只返回指定规格名称的值列表。 | |
| 396 | + | |
| 272 | 397 | ### 高级模式 |
| 273 | 398 | |
| 274 | 399 | #### 示例 1:自定义分面大小 |
| ... | ... | @@ -276,16 +401,18 @@ curl -X POST "http://localhost:6002/search/" \ |
| 276 | 401 | ```bash |
| 277 | 402 | curl -X POST "http://localhost:6002/search/" \ |
| 278 | 403 | -H "Content-Type: application/json" \ |
| 404 | + -H "X-Tenant-ID: 2" \ | |
| 279 | 405 | -d '{ |
| 280 | - "query": "玩具", | |
| 406 | + "query": "手机", | |
| 407 | + "language": "zh", | |
| 281 | 408 | "facets": [ |
| 282 | 409 | { |
| 283 | - "field": "category.keyword", | |
| 410 | + "field": "category1_name", | |
| 284 | 411 | "size": 20, |
| 285 | 412 | "type": "terms" |
| 286 | 413 | }, |
| 287 | 414 | { |
| 288 | - "field": "vendor.keyword", | |
| 415 | + "field": "category2_name", | |
| 289 | 416 | "size": 30, |
| 290 | 417 | "type": "terms" |
| 291 | 418 | } |
| ... | ... | @@ -298,8 +425,10 @@ curl -X POST "http://localhost:6002/search/" \ |
| 298 | 425 | ```bash |
| 299 | 426 | curl -X POST "http://localhost:6002/search/" \ |
| 300 | 427 | -H "Content-Type: application/json" \ |
| 428 | + -H "X-Tenant-ID: 2" \ | |
| 301 | 429 | -d '{ |
| 302 | - "query": "玩具", | |
| 430 | + "query": "手机", | |
| 431 | + "language": "zh", | |
| 303 | 432 | "facets": [ |
| 304 | 433 | { |
| 305 | 434 | "field": "price", |
| ... | ... | @@ -339,8 +468,10 @@ curl -X POST "http://localhost:6002/search/" \ |
| 339 | 468 | ```bash |
| 340 | 469 | curl -X POST "http://localhost:6002/search/" \ |
| 341 | 470 | -H "Content-Type: application/json" \ |
| 471 | + -H "X-Tenant-ID: 2" \ | |
| 342 | 472 | -d '{ |
| 343 | - "query": "玩具", | |
| 473 | + "query": "手机", | |
| 474 | + "language": "zh", | |
| 344 | 475 | "facets": [ |
| 345 | 476 | {"field": "category.keyword", "size": 15}, |
| 346 | 477 | {"field": "vendor.keyword", "size": 15}, |
| ... | ... | @@ -366,8 +497,10 @@ curl -X POST "http://localhost:6002/search/" \ |
| 366 | 497 | ```bash |
| 367 | 498 | curl -X POST "http://localhost:6002/search/" \ |
| 368 | 499 | -H "Content-Type: application/json" \ |
| 500 | + -H "X-Tenant-ID: 2" \ | |
| 369 | 501 | -d '{ |
| 370 | - "query": "玩具", | |
| 502 | + "query": "手机", | |
| 503 | + "language": "zh", | |
| 371 | 504 | "size": 20, |
| 372 | 505 | "sort_by": "min_price", |
| 373 | 506 | "sort_order": "asc" |
| ... | ... | @@ -379,8 +512,10 @@ curl -X POST "http://localhost:6002/search/" \ |
| 379 | 512 | ```bash |
| 380 | 513 | curl -X POST "http://localhost:6002/search/" \ |
| 381 | 514 | -H "Content-Type: application/json" \ |
| 515 | + -H "X-Tenant-ID: 2" \ | |
| 382 | 516 | -d '{ |
| 383 | - "query": "玩具", | |
| 517 | + "query": "手机", | |
| 518 | + "language": "zh", | |
| 384 | 519 | "size": 20, |
| 385 | 520 | "sort_by": "create_time", |
| 386 | 521 | "sort_order": "desc" |
| ... | ... | @@ -392,8 +527,10 @@ curl -X POST "http://localhost:6002/search/" \ |
| 392 | 527 | ```bash |
| 393 | 528 | curl -X POST "http://localhost:6002/search/" \ |
| 394 | 529 | -H "Content-Type: application/json" \ |
| 530 | + -H "X-Tenant-ID: 2" \ | |
| 395 | 531 | -d '{ |
| 396 | - "query": "玩具", | |
| 532 | + "query": "手机", | |
| 533 | + "language": "zh", | |
| 397 | 534 | "filters": { |
| 398 | 535 | "category.keyword": "益智玩具" |
| 399 | 536 | }, |
| ... | ... | @@ -411,6 +548,7 @@ curl -X POST "http://localhost:6002/search/" \ |
| 411 | 548 | ```bash |
| 412 | 549 | curl -X POST "http://localhost:6002/search/image" \ |
| 413 | 550 | -H "Content-Type: application/json" \ |
| 551 | + -H "X-Tenant-ID: 2" \ | |
| 414 | 552 | -d '{ |
| 415 | 553 | "image_url": "https://example.com/barbie.jpg", |
| 416 | 554 | "size": 20 |
| ... | ... | @@ -422,14 +560,15 @@ curl -X POST "http://localhost:6002/search/image" \ |
| 422 | 560 | ```bash |
| 423 | 561 | curl -X POST "http://localhost:6002/search/image" \ |
| 424 | 562 | -H "Content-Type: application/json" \ |
| 563 | + -H "X-Tenant-ID: 2" \ | |
| 425 | 564 | -d '{ |
| 426 | 565 | "image_url": "https://example.com/barbie.jpg", |
| 427 | 566 | "size": 20, |
| 428 | 567 | "filters": { |
| 429 | - "category.keyword": "玩具" | |
| 568 | + "category_name": "手机" | |
| 430 | 569 | }, |
| 431 | 570 | "range_filters": { |
| 432 | - "price": { | |
| 571 | + "min_price": { | |
| 433 | 572 | "lte": 100 |
| 434 | 573 | } |
| 435 | 574 | } |
| ... | ... | @@ -445,6 +584,7 @@ curl -X POST "http://localhost:6002/search/image" \ |
| 445 | 584 | ```bash |
| 446 | 585 | curl -X POST "http://localhost:6002/search/" \ |
| 447 | 586 | -H "Content-Type: application/json" \ |
| 587 | + -H "X-Tenant-ID: 2" \ | |
| 448 | 588 | -d '{ |
| 449 | 589 | "query": "玩具 AND 乐高" |
| 450 | 590 | }' |
| ... | ... | @@ -457,6 +597,7 @@ curl -X POST "http://localhost:6002/search/" \ |
| 457 | 597 | ```bash |
| 458 | 598 | curl -X POST "http://localhost:6002/search/" \ |
| 459 | 599 | -H "Content-Type: application/json" \ |
| 600 | + -H "X-Tenant-ID: 2" \ | |
| 460 | 601 | -d '{ |
| 461 | 602 | "query": "芭比 OR 娃娃" |
| 462 | 603 | }' |
| ... | ... | @@ -469,6 +610,7 @@ curl -X POST "http://localhost:6002/search/" \ |
| 469 | 610 | ```bash |
| 470 | 611 | curl -X POST "http://localhost:6002/search/" \ |
| 471 | 612 | -H "Content-Type: application/json" \ |
| 613 | + -H "X-Tenant-ID: 2" \ | |
| 472 | 614 | -d '{ |
| 473 | 615 | "query": "玩具 ANDNOT 电动" |
| 474 | 616 | }' |
| ... | ... | @@ -481,6 +623,7 @@ curl -X POST "http://localhost:6002/search/" \ |
| 481 | 623 | ```bash |
| 482 | 624 | curl -X POST "http://localhost:6002/search/" \ |
| 483 | 625 | -H "Content-Type: application/json" \ |
| 626 | + -H "X-Tenant-ID: 2" \ | |
| 484 | 627 | -d '{ |
| 485 | 628 | "query": "玩具 AND (乐高 OR 芭比) ANDNOT 电动" |
| 486 | 629 | }' |
| ... | ... | @@ -493,6 +636,7 @@ curl -X POST "http://localhost:6002/search/" \ |
| 493 | 636 | ```bash |
| 494 | 637 | curl -X POST "http://localhost:6002/search/" \ |
| 495 | 638 | -H "Content-Type: application/json" \ |
| 639 | + -H "X-Tenant-ID: 2" \ | |
| 496 | 640 | -d '{ |
| 497 | 641 | "query": "brand:乐高" |
| 498 | 642 | }' |
| ... | ... | @@ -557,19 +701,23 @@ for hit in result['hits'][:3]: |
| 557 | 701 | |
| 558 | 702 | # 示例 2:带过滤和分面的搜索 |
| 559 | 703 | result = search_products( |
| 560 | - query="玩具", | |
| 704 | + query="手机", | |
| 561 | 705 | size=20, |
| 706 | + language="zh", | |
| 562 | 707 | filters={ |
| 563 | - "category.keyword": ["玩具", "益智玩具"] | |
| 708 | + "category_name": "手机", | |
| 709 | + "specifications": {"name": "color", "value": "white"} | |
| 564 | 710 | }, |
| 565 | 711 | range_filters={ |
| 566 | - "price": {"gte": 50, "lte": 200} | |
| 712 | + "min_price": {"gte": 50, "lte": 200} | |
| 567 | 713 | }, |
| 568 | 714 | facets=[ |
| 569 | - {"field": "vendor.keyword", "size": 15}, | |
| 570 | - {"field": "category.keyword", "size": 15}, | |
| 715 | + {"field": "category1_name", "size": 15}, | |
| 716 | + {"field": "category2_name", "size": 15}, | |
| 717 | + "specifications.color", | |
| 718 | + "specifications.size", | |
| 571 | 719 | { |
| 572 | - "field": "price", | |
| 720 | + "field": "min_price", | |
| 573 | 721 | "type": "range", |
| 574 | 722 | "ranges": [ |
| 575 | 723 | {"key": "0-50", "to": 50}, |
| ... | ... | @@ -692,21 +840,24 @@ const result1 = await client.search({ |
| 692 | 840 | }); |
| 693 | 841 | console.log(`找到 ${result1.total} 个结果`); |
| 694 | 842 | |
| 695 | -// 带过滤和分面的搜索 | |
| 843 | +// 带过滤和分面的搜索(包含规格) | |
| 696 | 844 | const result2 = await client.search({ |
| 697 | - query: "玩具", | |
| 845 | + query: "手机", | |
| 846 | + language: "zh", | |
| 698 | 847 | size: 20, |
| 699 | 848 | filters: { |
| 700 | - category.keyword: ["玩具", "益智玩具"] | |
| 849 | + category_name: "手机", | |
| 850 | + specifications: { name: "color", value: "white" } | |
| 701 | 851 | }, |
| 702 | 852 | rangeFilters: { |
| 703 | - price: { gte: 50, lte: 200 } | |
| 853 | + min_price: { gte: 50, lte: 200 } | |
| 704 | 854 | }, |
| 705 | 855 | facets: [ |
| 706 | - { field: "vendor.keyword", size: 15 }, | |
| 707 | - { field: "category.keyword", size: 15 } | |
| 856 | + "category1_name", | |
| 857 | + "specifications.color", | |
| 858 | + "specifications.size" | |
| 708 | 859 | ], |
| 709 | - sortBy: "price", | |
| 860 | + sortBy: "min_price", | |
| 710 | 861 | sortOrder: "asc" |
| 711 | 862 | }); |
| 712 | 863 | |
| ... | ... | @@ -810,8 +961,10 @@ const SearchComponent = { |
| 810 | 961 | ```bash |
| 811 | 962 | curl -X POST "http://localhost:6002/search/" \ |
| 812 | 963 | -H "Content-Type: application/json" \ |
| 964 | + -H "X-Tenant-ID: 2" \ | |
| 813 | 965 | -d '{ |
| 814 | - "query": "玩具", | |
| 966 | + "query": "手机", | |
| 967 | + "language": "zh", | |
| 815 | 968 | "debug": true |
| 816 | 969 | }' |
| 817 | 970 | ``` |
| ... | ... | @@ -847,8 +1000,10 @@ curl -X POST "http://localhost:6002/search/" \ |
| 847 | 1000 | ```bash |
| 848 | 1001 | curl -X POST "http://localhost:6002/search/" \ |
| 849 | 1002 | -H "Content-Type: application/json" \ |
| 1003 | + -H "X-Tenant-ID: 2" \ | |
| 850 | 1004 | -d '{ |
| 851 | - "query": "玩具", | |
| 1005 | + "query": "手机", | |
| 1006 | + "language": "zh", | |
| 852 | 1007 | "min_score": 5.0 |
| 853 | 1008 | }' |
| 854 | 1009 | ``` |
| ... | ... | @@ -865,10 +1020,11 @@ curl -X POST "http://localhost:6002/search/" \ |
| 865 | 1020 | # 显示某个类目下的所有商品,按价格排序,提供品牌筛选 |
| 866 | 1021 | curl -X POST "http://localhost:6002/search/" \ |
| 867 | 1022 | -H "Content-Type: application/json" \ |
| 1023 | + -H "X-Tenant-ID: 2" \ | |
| 868 | 1024 | -d '{ |
| 869 | 1025 | "query": "*", |
| 870 | 1026 | "filters": { |
| 871 | - "category.keyword": "玩具" | |
| 1027 | + "category_name": "手机" | |
| 872 | 1028 | }, |
| 873 | 1029 | "facets": [ |
| 874 | 1030 | {"field": "vendor.keyword", "size": 20}, |
| ... | ... | @@ -892,19 +1048,53 @@ curl -X POST "http://localhost:6002/search/" \ |
| 892 | 1048 | ### 场景 2:搜索结果页 |
| 893 | 1049 | |
| 894 | 1050 | ```bash |
| 895 | -# 用户搜索关键词,提供筛选和排序 | |
| 1051 | +# 用户搜索关键词,提供筛选和排序(包含规格分面) | |
| 1052 | +curl -X POST "http://localhost:6002/search/" \ | |
| 1053 | + -H "Content-Type: application/json" \ | |
| 1054 | + -H "X-Tenant-ID: 2" \ | |
| 1055 | + -d '{ | |
| 1056 | + "query": "手机", | |
| 1057 | + "language": "zh", | |
| 1058 | + "facets": [ | |
| 1059 | + {"field": "category1_name", "size": 10}, | |
| 1060 | + {"field": "category2_name", "size": 10}, | |
| 1061 | + "specifications.color", | |
| 1062 | + "specifications.size", | |
| 1063 | + { | |
| 1064 | + "field": "min_price", | |
| 1065 | + "type": "range", | |
| 1066 | + "ranges": [ | |
| 1067 | + {"key": "0-50", "to": 50}, | |
| 1068 | + {"key": "50-100", "from": 50, "to": 100}, | |
| 1069 | + {"key": "100+", "from": 100} | |
| 1070 | + ] | |
| 1071 | + } | |
| 1072 | + ], | |
| 1073 | + "size": 20 | |
| 1074 | + }' | |
| 1075 | +``` | |
| 1076 | + | |
| 1077 | +### 场景 2.1:带规格过滤的搜索结果页 | |
| 1078 | + | |
| 1079 | +```bash | |
| 1080 | +# 用户搜索并选择了规格筛选条件 | |
| 896 | 1081 | curl -X POST "http://localhost:6002/search/" \ |
| 897 | 1082 | -H "Content-Type: application/json" \ |
| 1083 | + -H "X-Tenant-ID: 2" \ | |
| 898 | 1084 | -d '{ |
| 899 | - "query": "芭比娃娃", | |
| 1085 | + "query": "手机", | |
| 1086 | + "language": "zh", | |
| 1087 | + "filters": { | |
| 1088 | + "category_name": "手机", | |
| 1089 | + "specifications": { | |
| 1090 | + "name": "color", | |
| 1091 | + "value": "white" | |
| 1092 | + } | |
| 1093 | + }, | |
| 900 | 1094 | "facets": [ |
| 901 | - {"field": "category.keyword", "size": 10}, | |
| 902 | - {"field": "vendor.keyword", "size": 10}, | |
| 903 | - {"field": "price", "type": "range", "ranges": [ | |
| 904 | - {"key": "0-50", "to": 50}, | |
| 905 | - {"key": "50-100", "from": 50, "to": 100}, | |
| 906 | - {"key": "100+", "from": 100} | |
| 907 | - ]} | |
| 1095 | + "category1_name", | |
| 1096 | + "specifications.color", | |
| 1097 | + "specifications.size" | |
| 908 | 1098 | ], |
| 909 | 1099 | "size": 20 |
| 910 | 1100 | }' |
| ... | ... | @@ -916,15 +1106,16 @@ curl -X POST "http://localhost:6002/search/" \ |
| 916 | 1106 | # 显示特定价格区间的商品 |
| 917 | 1107 | curl -X POST "http://localhost:6002/search/" \ |
| 918 | 1108 | -H "Content-Type: application/json" \ |
| 1109 | + -H "X-Tenant-ID: 2" \ | |
| 919 | 1110 | -d '{ |
| 920 | 1111 | "query": "*", |
| 921 | 1112 | "range_filters": { |
| 922 | - "price": { | |
| 1113 | + "min_price": { | |
| 923 | 1114 | "gte": 50, |
| 924 | 1115 | "lte": 100 |
| 925 | 1116 | } |
| 926 | 1117 | }, |
| 927 | - "facets": ["category.keyword", "vendor.keyword"], | |
| 1118 | + "facets": ["category1_name", "category2_name", "specifications"], | |
| 928 | 1119 | "sort_by": "min_price", |
| 929 | 1120 | "sort_order": "asc", |
| 930 | 1121 | "size": 50 |
| ... | ... | @@ -937,6 +1128,7 @@ curl -X POST "http://localhost:6002/search/" \ |
| 937 | 1128 | # 最近更新的商品 |
| 938 | 1129 | curl -X POST "http://localhost:6002/search/" \ |
| 939 | 1130 | -H "Content-Type: application/json" \ |
| 1131 | + -H "X-Tenant-ID: 2" \ | |
| 940 | 1132 | -d '{ |
| 941 | 1133 | "query": "*", |
| 942 | 1134 | "range_filters": { |
| ... | ... | @@ -960,10 +1152,12 @@ curl -X POST "http://localhost:6002/search/" \ |
| 960 | 1152 | # 错误:range_filters 缺少操作符 |
| 961 | 1153 | curl -X POST "http://localhost:6002/search/" \ |
| 962 | 1154 | -H "Content-Type: application/json" \ |
| 1155 | + -H "X-Tenant-ID: 2" \ | |
| 963 | 1156 | -d '{ |
| 964 | - "query": "玩具", | |
| 1157 | + "query": "手机", | |
| 1158 | + "language": "zh", | |
| 965 | 1159 | "range_filters": { |
| 966 | - "price": {} | |
| 1160 | + "min_price": {} | |
| 967 | 1161 | } |
| 968 | 1162 | }' |
| 969 | 1163 | ``` |
| ... | ... | @@ -983,6 +1177,7 @@ curl -X POST "http://localhost:6002/search/" \ |
| 983 | 1177 | # 错误:query 为空 |
| 984 | 1178 | curl -X POST "http://localhost:6002/search/" \ |
| 985 | 1179 | -H "Content-Type: application/json" \ |
| 1180 | + -H "X-Tenant-ID: 2" \ | |
| 986 | 1181 | -d '{ |
| 987 | 1182 | "query": "" |
| 988 | 1183 | }' |
| ... | ... | @@ -1060,6 +1255,7 @@ curl -X POST "http://localhost:6002/search/" \ |
| 1060 | 1255 | # 使用通配符查询 + 分面 |
| 1061 | 1256 | curl -X POST "http://localhost:6002/search/" \ |
| 1062 | 1257 | -H "Content-Type: application/json" \ |
| 1258 | + -H "X-Tenant-ID: 2" \ | |
| 1063 | 1259 | -d '{ |
| 1064 | 1260 | "query": "*", |
| 1065 | 1261 | "size": 0, |
| ... | ... | @@ -1074,8 +1270,10 @@ curl -X POST "http://localhost:6002/search/" \ |
| 1074 | 1270 | ```bash |
| 1075 | 1271 | curl -X POST "http://localhost:6002/search/" \ |
| 1076 | 1272 | -H "Content-Type: application/json" \ |
| 1273 | + -H "X-Tenant-ID: 2" \ | |
| 1077 | 1274 | -d '{ |
| 1078 | - "query": "玩具", | |
| 1275 | + "query": "手机", | |
| 1276 | + "language": "zh", | |
| 1079 | 1277 | "size": 0, |
| 1080 | 1278 | "facets": [ |
| 1081 | 1279 | { |
| ... | ... | @@ -1099,13 +1297,14 @@ curl -X POST "http://localhost:6002/search/" \ |
| 1099 | 1297 | # 布尔表达式 + 过滤器 + 分面 + 排序 |
| 1100 | 1298 | curl -X POST "http://localhost:6002/search/" \ |
| 1101 | 1299 | -H "Content-Type: application/json" \ |
| 1300 | + -H "X-Tenant-ID: 2" \ | |
| 1102 | 1301 | -d '{ |
| 1103 | 1302 | "query": "(玩具 OR 游戏) AND 儿童 ANDNOT 电子", |
| 1104 | 1303 | "filters": { |
| 1105 | 1304 | "category.keyword": ["玩具", "益智玩具"] |
| 1106 | 1305 | }, |
| 1107 | 1306 | "range_filters": { |
| 1108 | - "price": {"gte": 20, "lte": 100}, | |
| 1307 | + "min_price": {"gte": 20, "lte": 100}, | |
| 1109 | 1308 | "days_since_last_update": {"lte": 30} |
| 1110 | 1309 | }, |
| 1111 | 1310 | "facets": [ |
| ... | ... | @@ -1127,16 +1326,19 @@ curl -X POST "http://localhost:6002/search/" \ |
| 1127 | 1326 | # 测试类目:玩具 |
| 1128 | 1327 | curl -X POST "http://localhost:6002/search/" \ |
| 1129 | 1328 | -H "Content-Type: application/json" \ |
| 1329 | + -H "X-Tenant-ID: 2" \ | |
| 1130 | 1330 | -d '{"query": "玩具", "size": 5}' |
| 1131 | 1331 | |
| 1132 | 1332 | # 测试品牌:乐高 |
| 1133 | 1333 | curl -X POST "http://localhost:6002/search/" \ |
| 1134 | 1334 | -H "Content-Type: application/json" \ |
| 1335 | + -H "X-Tenant-ID: 2" \ | |
| 1135 | 1336 | -d '{"query": "brand:乐高", "size": 5}' |
| 1136 | 1337 | |
| 1137 | 1338 | # 测试布尔表达式 |
| 1138 | 1339 | curl -X POST "http://localhost:6002/search/" \ |
| 1139 | 1340 | -H "Content-Type: application/json" \ |
| 1341 | + -H "X-Tenant-ID: 2" \ | |
| 1140 | 1342 | -d '{"query": "玩具 AND 乐高", "size": 5}' |
| 1141 | 1343 | ``` |
| 1142 | 1344 | ... | ... |
docs/基础配置指南.md
| 1 | -# Base Configuration Guide | |
| 2 | - | |
| 3 | -店匠通用配置(Base Configuration)使用指南 | |
| 1 | +# 基础配置指南 | |
| 4 | 2 | |
| 5 | 3 | ## 概述 |
| 6 | 4 | |
| 7 | -Base配置是店匠(Shoplazza)通用配置,适用于所有使用店匠标准表的客户。该配置采用SPU级别的索引结构,所有客户共享同一个Elasticsearch索引(`search_products`),通过`tenant_id`字段实现数据隔离。 | |
| 5 | +搜索引擎采用**统一硬编码配置**方案,所有租户共享相同的索引结构和查询配置,无需单独配置。 | |
| 8 | 6 | |
| 9 | 7 | ## 核心特性 |
| 10 | 8 | |
| 11 | -- **SPU级别索引**:每个ES文档代表一个SPU,包含嵌套的skus数组 | |
| 12 | -- **统一索引**:所有客户共享`search_products`索引 | |
| 13 | -- **租户隔离**:通过`tenant_id`字段实现数据隔离 | |
| 14 | -- **配置简化**:配置只包含ES搜索相关配置,不包含MySQL数据源配置 | |
| 15 | -- **外部友好格式**:API返回格式不包含ES内部字段(`_id`, `_score`, `_source`) | |
| 9 | +- **统一索引结构**: 所有租户共享 `search_products` 索引 | |
| 10 | +- **硬编码配置**: 索引 mapping 和查询配置直接硬编码在代码中,无需配置文件 | |
| 11 | +- **SPU级别索引**: 每个ES文档代表一个SPU,包含嵌套的 `skus` 和 `specifications` 数组 | |
| 12 | +- **租户隔离**: 通过 `tenant_id` 字段实现数据隔离 | |
| 13 | +- **多语言支持**: 文本字段支持中英文双语,后端根据 `language` 参数自动选择 | |
| 16 | 14 | |
| 17 | -## 配置说明 | |
| 15 | +## 索引结构 | |
| 18 | 16 | |
| 19 | -### 配置文件位置 | |
| 17 | +### Mapping 文件位置 | |
| 20 | 18 | |
| 21 | -`config/schema/base/config.yaml` | |
| 19 | +`mappings/search_products.json` | |
| 22 | 20 | |
| 23 | -### 配置内容 | |
| 21 | +### 主要字段 | |
| 24 | 22 | |
| 25 | -Base配置**不包含**以下内容: | |
| 26 | -- `mysql_config` - MySQL数据库配置 | |
| 27 | -- `main_table` - 主表配置 | |
| 28 | -- `extension_table` - 扩展表配置 | |
| 29 | -- `source_table` / `source_column` - 字段数据源映射 | |
| 23 | +#### 基础标识 | |
| 24 | +- `tenant_id` (keyword) - 租户ID(必需,用于隔离) | |
| 25 | +- `spu_id` (keyword) - SPU ID | |
| 26 | +- `create_time`, `update_time` (date) - 时间字段 | |
| 30 | 27 | |
| 31 | -Base配置**只包含**: | |
| 32 | -- ES字段定义(字段类型、分析器、boost等) | |
| 33 | -- 查询域(indexes)配置 | |
| 34 | -- 查询处理配置(query_config) | |
| 35 | -- 排序和打分配置(function_score) | |
| 36 | -- SPU配置(spu_config) | |
| 28 | +#### 多语言文本字段 | |
| 29 | +- `title_zh`, `title_en` (text) - 标题(中英文) | |
| 30 | +- `brief_zh`, `brief_en` (text) - 短描述(中英文) | |
| 31 | +- `description_zh`, `description_en` (text) - 详细描述(中英文) | |
| 32 | +- `vendor_zh`, `vendor_en` (text) - 供应商/品牌(中英文,含keyword子字段) | |
| 33 | +- `category_path_zh`, `category_path_en` (text) - 类目路径(中英文) | |
| 34 | +- `category_name_zh`, `category_name_en` (text) - 类目名称(中英文) | |
| 37 | 35 | |
| 38 | -### 必需字段 | |
| 36 | +#### 类目字段 | |
| 37 | +- `category_id` (keyword) - 类目ID | |
| 38 | +- `category_name` (keyword) - 类目名称 | |
| 39 | +- `category_level` (integer) - 类目层级 | |
| 40 | +- `category1_name`, `category2_name`, `category3_name` (keyword) - 多级类目 | |
| 39 | 41 | |
| 40 | -- `tenant_id` (KEYWORD, required) - 租户隔离字段 | |
| 42 | +#### 规格和选项 | |
| 43 | +- `specifications` (nested) - 规格列表(name, value, sku_id) | |
| 44 | +- `option1_name`, `option2_name`, `option3_name` (keyword) - 选项名称 | |
| 41 | 45 | |
| 42 | -### 主要字段 | |
| 46 | +#### 价格和库存 | |
| 47 | +- `min_price`, `max_price`, `compare_at_price` (float) - 价格字段 | |
| 48 | +- `sku_prices` (float) - SKU价格列表(数组) | |
| 49 | +- `sku_weights` (long) - SKU重量列表(数组) | |
| 50 | +- `sku_weight_units` (keyword) - SKU重量单位列表(数组) | |
| 51 | +- `total_inventory` (long) - 总库存 | |
| 43 | 52 | |
| 44 | -- `spu_id` - SPU ID | |
| 45 | -- `title`, `brief`, `description` - 文本搜索字段 | |
| 46 | -- `seo_title`, `seo_description`, `seo_keywords` - SEO字段 | |
| 47 | -- `vendor`, `tags`, `category` - 分类和标签字段(HKText,支持 `.keyword` 精确匹配) | |
| 48 | -- `min_price`, `max_price`, `compare_at_price` - 价格字段 | |
| 49 | -- `skus` (nested) - 嵌套SKU数组 | |
| 53 | +#### 嵌套字段 | |
| 54 | +- `skus` (nested) - SKU详细信息数组 | |
| 55 | +- `image_embedding` (nested) - 图片向量(仅用于搜索) | |
| 50 | 56 | |
| 51 | -## 数据导入流程 | |
| 57 | +#### 其他 | |
| 58 | +- `tags` (keyword) - 标签列表(数组) | |
| 59 | +- `image_url` (keyword, index: false) - 主图URL | |
| 60 | +- `title_embedding` (dense_vector) - 标题向量(仅用于搜索,不返回) | |
| 52 | 61 | |
| 53 | -### 1. 生成测试数据 | |
| 62 | +## 查询配置 | |
| 54 | 63 | |
| 55 | -```bash | |
| 56 | -python scripts/generate_test_data.py \ | |
| 57 | - --num-spus 100 \ | |
| 58 | - --tenant-id "1" \ | |
| 59 | - --start-spu-id 1 \ | |
| 60 | - --start-sku-id 1 \ | |
| 61 | - --output test_data.sql | |
| 62 | -``` | |
| 64 | +### 文本召回字段 | |
| 63 | 65 | |
| 64 | -### 2. 导入测试数据到MySQL | |
| 65 | - | |
| 66 | -```bash | |
| 67 | -python scripts/import_test_data.py \ | |
| 68 | - --db-host localhost \ | |
| 69 | - --db-port 3306 \ | |
| 70 | - --db-database saas \ | |
| 71 | - --db-username root \ | |
| 72 | - --db-password password \ | |
| 73 | - --sql-file test_data.sql \ | |
| 74 | - --tenant-id "1" | |
| 75 | -``` | |
| 66 | +默认同时搜索以下字段(中英文都包含): | |
| 67 | +- `title_zh^3.0`, `title_en^3.0` | |
| 68 | +- `brief_zh^1.5`, `brief_en^1.5` | |
| 69 | +- `description_zh^1.0`, `description_en^1.0` | |
| 70 | +- `vendor_zh^1.5`, `vendor_en^1.5` | |
| 71 | +- `category_path_zh^1.5`, `category_path_en^1.5` | |
| 72 | +- `category_name_zh^1.5`, `category_name_en^1.5` | |
| 73 | +- `tags^1.0` | |
| 76 | 74 | |
| 77 | -### 3. 导入数据到Elasticsearch | |
| 78 | - | |
| 79 | -```bash | |
| 80 | -python scripts/ingest_shoplazza.py \ | |
| 81 | - --db-host localhost \ | |
| 82 | - --db-port 3306 \ | |
| 83 | - --db-database saas \ | |
| 84 | - --db-username root \ | |
| 85 | - --db-password password \ | |
| 86 | - --tenant-id "1" \ | |
| 87 | - --config base \ | |
| 88 | - --es-host http://localhost:9200 \ | |
| 89 | - --recreate \ | |
| 90 | - --batch-size 500 | |
| 91 | -``` | |
| 75 | +### 查询架构 | |
| 92 | 76 | |
| 93 | -## API使用 | |
| 77 | +**结构**: `filters AND (text_recall OR embedding_recall)` | |
| 94 | 78 | |
| 95 | -### 搜索接口 | |
| 79 | +- **filters**: 前端传递的过滤条件(永远起作用) | |
| 80 | +- **text_recall**: 文本相关性召回(同时搜索中英文字段) | |
| 81 | +- **embedding_recall**: 向量召回(KNN,使用 `title_embedding`) | |
| 82 | +- **function_score**: 包装召回部分,支持提权字段 | |
| 96 | 83 | |
| 97 | -**端点**: `POST /search/` | |
| 84 | +### Function Score 配置 | |
| 98 | 85 | |
| 99 | -**请求头**: | |
| 100 | -``` | |
| 101 | -X-Tenant-ID: 1 | |
| 102 | -Content-Type: application/json | |
| 103 | -``` | |
| 86 | +位置: `search/query_config.py` 中的 `FUNCTION_SCORE_CONFIG` | |
| 104 | 87 | |
| 105 | -**请求体**: | |
| 106 | -```json | |
| 107 | -{ | |
| 108 | - "query": "耳机", | |
| 109 | - "size": 10, | |
| 110 | - "from": 0, | |
| 111 | - "filters": { | |
| 112 | - "category.keyword": "电子产品" | |
| 113 | - }, | |
| 114 | - "facets": ["category.keyword", "vendor.keyword"] | |
| 115 | -} | |
| 116 | -``` | |
| 88 | +支持的类型: | |
| 89 | +- `filter_weight`: 条件权重(如新品提权) | |
| 90 | +- `field_value_factor`: 字段值因子(如销量因子) | |
| 91 | +- `decay`: 衰减函数(如时间衰减) | |
| 92 | + | |
| 93 | +## 分面配置 | |
| 94 | + | |
| 95 | +### 默认分面字段 | |
| 96 | + | |
| 97 | +- `category1_name` - 一级类目 | |
| 98 | +- `category2_name` - 二级类目 | |
| 99 | +- `category3_name` - 三级类目 | |
| 100 | +- `specifications` - 规格分面(嵌套聚合,按name分组,然后按value聚合) | |
| 101 | + | |
| 102 | +### 规格分面说明 | |
| 103 | + | |
| 104 | +`specifications` 使用特殊的嵌套聚合: | |
| 105 | +- 按 `specifications.name` 分组(如"color"、"size") | |
| 106 | +- 每个 `name` 下按 `specifications.value` 聚合(如"white"、"black") | |
| 117 | 107 | |
| 118 | -**响应格式**: | |
| 108 | +返回格式: | |
| 119 | 109 | ```json |
| 120 | 110 | { |
| 121 | - "results": [ | |
| 122 | - { | |
| 123 | - "spu_id": "1", | |
| 124 | - "title": "蓝牙耳机 Sony", | |
| 125 | - "handle": "product-1", | |
| 126 | - "description": "高品质无线蓝牙耳机", | |
| 127 | - "vendor": "Sony", | |
| 128 | - "category": "电子产品", | |
| 129 | - "price": 199.99, | |
| 130 | - "compare_at_price": 299.99, | |
| 131 | - "currency": "USD", | |
| 132 | - "image_url": "//cdn.example.com/products/1.jpg", | |
| 133 | - "in_stock": true, | |
| 134 | - "skus": [ | |
| 135 | - { | |
| 136 | - "sku_id": "1", | |
| 137 | - "title": "黑色", | |
| 138 | - "price": 199.99, | |
| 139 | - "compare_at_price": 299.99, | |
| 140 | - "sku": "SKU-1-1", | |
| 141 | - "stock": 50, | |
| 142 | - "options": { | |
| 143 | - "option1": "黑色" | |
| 144 | - } | |
| 145 | - } | |
| 146 | - ], | |
| 147 | - "relevance_score": 0.95 | |
| 148 | - } | |
| 149 | - ], | |
| 150 | - "total": 10, | |
| 151 | - "max_score": 1.0, | |
| 152 | - "facets": [ | |
| 153 | - { | |
| 154 | - "field": "category.keyword", | |
| 155 | - "label": "category.keyword", | |
| 156 | - "type": "terms", | |
| 157 | - "values": [ | |
| 158 | - { | |
| 159 | - "value": "电子产品", | |
| 160 | - "label": "电子产品", | |
| 161 | - "count": 5, | |
| 162 | - "selected": false | |
| 163 | - } | |
| 164 | - ] | |
| 165 | - } | |
| 166 | - ], | |
| 167 | - "suggestions": [], | |
| 168 | - "related_searches": [], | |
| 169 | - "took_ms": 15, | |
| 170 | - "query_info": {} | |
| 111 | + "field": "specifications.color", | |
| 112 | + "label": "color", | |
| 113 | + "type": "terms", | |
| 114 | + "values": [ | |
| 115 | + {"value": "white", "count": 50}, | |
| 116 | + {"value": "black", "count": 30} | |
| 117 | + ] | |
| 171 | 118 | } |
| 172 | 119 | ``` |
| 173 | 120 | |
| 174 | -### 响应格式说明 | |
| 175 | - | |
| 176 | -#### 主要变化 | |
| 177 | - | |
| 178 | -1. **`results`替代`hits`**:返回字段从`hits`改为`results` | |
| 179 | -2. **结构化结果**:每个结果包含`spu_id`, `title`, `skus`, `relevance_score`等字段 | |
| 180 | -3. **无ES内部字段**:不包含`_id`, `_score`, `_source`等ES内部字段 | |
| 181 | -4. **嵌套skus**:每个商品包含skus数组,每个sku包含完整的变体信息 | |
| 182 | -5. **相关性分数**:`relevance_score`是ES原始分数(不进行归一化) | |
| 183 | - | |
| 184 | -#### SpuResult字段 | |
| 185 | - | |
| 186 | -- `spu_id` - SPU ID | |
| 187 | -- `title` - 商品标题 | |
| 188 | -- `handle` - 商品handle | |
| 189 | -- `description` - 商品描述 | |
| 190 | -- `vendor` - 供应商/品牌 | |
| 191 | -- `category` - 类目 | |
| 192 | -- `tags` - 标签 | |
| 193 | -- `price` - 最低价格(min_price) | |
| 194 | -- `compare_at_price` - 原价 | |
| 195 | -- `currency` - 货币单位(默认USD) | |
| 196 | -- `image_url` - 主图URL | |
| 197 | -- `in_stock` - 是否有库存 | |
| 198 | -- `skus` - SKU列表 | |
| 199 | -- `relevance_score` - 相关性分数(ES原始分数) | |
| 200 | - | |
| 201 | -#### SkuResult字段 | |
| 202 | - | |
| 203 | -- `sku_id` - SKU ID | |
| 204 | -- `title` - 变体标题 | |
| 205 | -- `price` - 价格 | |
| 206 | -- `compare_at_price` - 原价 | |
| 207 | -- `sku` - SKU编码 | |
| 208 | -- `stock` - 库存数量 | |
| 209 | -- `options` - 选项(颜色、尺寸等) | |
| 210 | - | |
| 211 | -## 测试 | |
| 212 | - | |
| 213 | -### 运行测试脚本 | |
| 214 | - | |
| 215 | -```bash | |
| 216 | -python scripts/test_base.py \ | |
| 217 | - --api-url http://localhost:8000 \ | |
| 218 | - --tenant-id "1" \ | |
| 219 | - --test-tenant-2 "2" | |
| 220 | -``` | |
| 221 | - | |
| 222 | -### 测试内容 | |
| 223 | - | |
| 224 | -1. **基本搜索**:测试搜索API基本功能 | |
| 225 | -2. **响应格式验证**:验证返回格式是否符合要求 | |
| 226 | -3. **Facets聚合**:测试分面搜索功能 | |
| 227 | -4. **租户隔离**:验证不同租户的数据隔离 | |
| 121 | +## 返回字段映射 | |
| 228 | 122 | |
| 229 | -## 常见问题 | |
| 123 | +后端根据请求的 `language` 参数(`zh` 或 `en`)自动选择对应的中英文字段: | |
| 230 | 124 | |
| 231 | -### Q: 为什么配置中没有MySQL相关配置? | |
| 125 | +- `language="zh"`: 优先返回 `*_zh` 字段,如果为空则回退到 `*_en` 字段 | |
| 126 | +- `language="en"`: 优先返回 `*_en` 字段,如果为空则回退到 `*_zh` 字段 | |
| 232 | 127 | |
| 233 | -A: 数据源配置和数据导入流程是写死的脚本,不在搜索配置中。搜索配置只关注ES搜索相关的内容。 | |
| 128 | +映射规则: | |
| 129 | +- `title_zh/en` → `title` | |
| 130 | +- `brief_zh/en` → `brief` | |
| 131 | +- `description_zh/en` → `description` | |
| 132 | +- `vendor_zh/en` → `vendor` | |
| 133 | +- `category_path_zh/en` → `category_path` | |
| 134 | +- `category_name_zh/en` → `category_name` | |
| 234 | 135 | |
| 235 | -### Q: 如何为新的租户导入数据? | |
| 136 | +## 配置修改 | |
| 236 | 137 | |
| 237 | -A: 使用`ingest_shoplazza.py`脚本,指定不同的`--tenant-id`参数即可。 | |
| 138 | +### 修改索引结构 | |
| 238 | 139 | |
| 239 | -### Q: 如何验证租户隔离是否生效? | |
| 140 | +编辑 `mappings/search_products.json`,然后: | |
| 141 | +1. 删除旧索引: `scripts/recreate_and_import.py --recreate` | |
| 142 | +2. 重新导入数据: `scripts/ingest.sh <tenant_id> true` | |
| 240 | 143 | |
| 241 | -A: 使用`test_base.py`脚本,指定两个不同的`--tenant-id`,检查搜索结果是否隔离。 | |
| 144 | +### 修改查询配置 | |
| 242 | 145 | |
| 243 | -### Q: API返回格式中为什么没有`_id`和`_score`? | |
| 146 | +编辑 `search/query_config.py`: | |
| 147 | +- `DEFAULT_MATCH_FIELDS`: 文本召回字段列表 | |
| 148 | +- `FUNCTION_SCORE_CONFIG`: Function score 配置 | |
| 149 | +- `DEFAULT_FACETS`: 默认分面字段 | |
| 244 | 150 | |
| 245 | -A: 为了提供外部友好的API格式,我们移除了ES内部字段,使用`spu_id`和`relevance_score`替代。 | |
| 151 | +### 修改返回字段 | |
| 246 | 152 | |
| 247 | -### Q: 如何添加新的搜索字段? | |
| 248 | - | |
| 249 | -A: 在`config/schema/base/config.yaml`中添加字段定义,然后重新生成索引映射并重新导入数据。 | |
| 153 | +编辑 `search/query_config.py` 中的 `SOURCE_FIELDS` 列表。 | |
| 250 | 154 | |
| 251 | 155 | ## 注意事项 |
| 252 | 156 | |
| 253 | -1. **tenant_id必需**:所有API请求必须提供`tenant_id`(通过请求头`X-Tenant-ID`或查询参数`tenant_id`) | |
| 254 | -2. **索引共享**:所有客户共享`search_products`索引,确保`tenant_id`字段正确设置 | |
| 255 | -3. **数据导入**:数据导入脚本是写死的,不依赖配置中的MySQL设置 | |
| 256 | -4. **配置分离**:搜索配置和数据源配置完全分离,提高可维护性 | |
| 157 | +1. **无需配置文件**: 所有配置都是硬编码的,不需要为每个租户创建配置文件 | |
| 158 | +2. **统一结构**: 所有租户共享相同的索引结构和查询逻辑 | |
| 159 | +3. **多租户隔离**: 所有查询必须包含 `tenant_id` 过滤条件 | |
| 160 | +4. **向量字段**: `title_embedding` 和 `image_embedding` 仅用于搜索,不会返回给前端 | |
| 161 | + | |
| 162 | +## 相关文档 | |
| 257 | 163 | |
| 164 | +- `索引字段说明v2.md` - 详细的字段说明 | |
| 165 | +- `搜索API对接指南.md` - API使用说明 | |
| 166 | +- `mappings/search_products.json` - 索引 mapping 定义 | ... | ... |
docs/搜索API对接指南.md
| ... | ... | @@ -64,7 +64,7 @@ curl -X POST "http://120.76.41.98:6002/search/" \ |
| 64 | 64 | -d '{ |
| 65 | 65 | "tenant_id": "demo-tenant", |
| 66 | 66 | "query": "芭比娃娃", |
| 67 | - "facets": ["category.keyword", "vendor.keyword"], | |
| 67 | + "facets": ["category.keyword", "specifications.color", "specifications.size"], | |
| 68 | 68 | "min_score": 0.2 |
| 69 | 69 | }' |
| 70 | 70 | ``` |
| ... | ... | @@ -95,10 +95,10 @@ curl -X POST "http://120.76.41.98:6002/search/" \ |
| 95 | 95 | |
| 96 | 96 | ```json |
| 97 | 97 | { |
| 98 | - "tenant_id": "string (required)", | |
| 99 | 98 | "query": "string (required)", |
| 100 | 99 | "size": 10, |
| 101 | 100 | "from": 0, |
| 101 | + "language": "zh", | |
| 102 | 102 | "filters": {}, |
| 103 | 103 | "range_filters": {}, |
| 104 | 104 | "facets": [], |
| ... | ... | @@ -111,18 +111,20 @@ curl -X POST "http://120.76.41.98:6002/search/" \ |
| 111 | 111 | } |
| 112 | 112 | ``` |
| 113 | 113 | |
| 114 | +**注意**: `tenant_id` 通过 HTTP Header `X-Tenant-ID` 传递,不在请求体中。 | |
| 115 | + | |
| 114 | 116 | #### 参数详细说明 |
| 115 | 117 | |
| 116 | 118 | | 参数 | 类型 | 必填 | 默认值 | 说明 | |
| 117 | 119 | |------|------|------|--------|------| |
| 118 | -| `tenant_id` | string | Y | - | 租户ID,用于隔离不同站点或客户的数据 | | |
| 119 | 120 | | `query` | string | Y | - | 搜索查询字符串,支持布尔表达式(AND, OR, RANK, ANDNOT) | |
| 120 | 121 | | `size` | integer | N | 10 | 返回结果数量(1-100) | |
| 121 | 122 | | `from` | integer | N | 0 | 分页偏移量(用于分页) | |
| 123 | +| `language` | string | N | "zh" | 返回语言:`zh`(中文)或 `en`(英文)。后端会根据此参数选择对应的中英文字段返回 | | |
| 122 | 124 | | `filters` | object | N | null | 精确匹配过滤器(见下文) | |
| 123 | 125 | | `range_filters` | object | N | null | 数值范围过滤器(见下文) | |
| 124 | 126 | | `facets` | array | N | null | 分面配置(见下文) | |
| 125 | -| `sort_by` | string | N | null | 排序字段名(如 `min_price`, `max_price`, `title`) | | |
| 127 | +| `sort_by` | string | N | null | 排序字段名(如 `min_price`, `max_price`) | | |
| 126 | 128 | | `sort_order` | string | N | "desc" | 排序方向:`asc`(升序)或 `desc`(降序) | |
| 127 | 129 | | `min_score` | float | N | null | 最小相关性分数阈值 | |
| 128 | 130 | | `debug` | boolean | N | false | 是否返回调试信息 | |
| ... | ... | @@ -139,9 +141,17 @@ curl -X POST "http://120.76.41.98:6002/search/" \ |
| 139 | 141 | ```json |
| 140 | 142 | { |
| 141 | 143 | "filters": { |
| 142 | - "category.keyword": "玩具", // 单值:精确匹配 | |
| 143 | - "vendor.keyword": ["乐高", "孩之宝"], // 数组:匹配任意值(OR) | |
| 144 | - "tags.keyword": "益智玩具" // 单值:精确匹配 | |
| 144 | + "category_name": "手机", // 单值:精确匹配 | |
| 145 | + "category1_name": "服装", // 一级类目 | |
| 146 | + "category2_name": "男装", // 二级类目 | |
| 147 | + "category3_name": "衬衫", // 三级类目 | |
| 148 | + "vendor_zh.keyword": ["奇乐", "品牌A"], // 数组:匹配任意值(OR) | |
| 149 | + "tags": "手机", // 标签(keyword类型) | |
| 150 | + // specifications 嵌套过滤(特殊格式) | |
| 151 | + "specifications": { | |
| 152 | + "name": "color", | |
| 153 | + "value": "white" | |
| 154 | + } | |
| 145 | 155 | } |
| 146 | 156 | } |
| 147 | 157 | ``` |
| ... | ... | @@ -151,11 +161,46 @@ curl -X POST "http://120.76.41.98:6002/search/" \ |
| 151 | 161 | - 整数:精确匹配 |
| 152 | 162 | - 布尔值:精确匹配 |
| 153 | 163 | - 数组:匹配任意值(OR 逻辑) |
| 164 | +- 对象:specifications 嵌套过滤(见下文) | |
| 165 | + | |
| 166 | +**Specifications 嵌套过滤**: | |
| 167 | + | |
| 168 | +`specifications` 是嵌套字段,支持按规格名称和值进行过滤。 | |
| 169 | + | |
| 170 | +**单个规格过滤**: | |
| 171 | +```json | |
| 172 | +{ | |
| 173 | + "filters": { | |
| 174 | + "specifications": { | |
| 175 | + "name": "color", | |
| 176 | + "value": "white" | |
| 177 | + } | |
| 178 | + } | |
| 179 | +} | |
| 180 | +``` | |
| 181 | +查询规格名称为"color"且值为"white"的商品。 | |
| 182 | + | |
| 183 | +**多个规格过滤(OR 逻辑)**: | |
| 184 | +```json | |
| 185 | +{ | |
| 186 | + "filters": { | |
| 187 | + "specifications": [ | |
| 188 | + {"name": "color", "value": "white"}, | |
| 189 | + {"name": "size", "value": "256GB"} | |
| 190 | + ] | |
| 191 | + } | |
| 192 | +} | |
| 193 | +``` | |
| 194 | +查询满足任意一个规格的商品(color=white **或** size=256GB)。 | |
| 154 | 195 | |
| 155 | 196 | **常用过滤字段**: |
| 156 | -- `category.keyword`: 类目 | |
| 157 | -- `vendor.keyword`: 品牌/供应商 | |
| 158 | -- `tags.keyword`: 标签 | |
| 197 | +- `category_name`: 类目名称 | |
| 198 | +- `category1_name`, `category2_name`, `category3_name`: 多级类目 | |
| 199 | +- `category_id`: 类目ID | |
| 200 | +- `vendor_zh.keyword`, `vendor_en.keyword`: 供应商/品牌(使用keyword子字段) | |
| 201 | +- `tags`: 标签(keyword类型,支持数组) | |
| 202 | +- `option1_name`, `option2_name`, `option3_name`: 选项名称 | |
| 203 | +- `specifications`: 规格过滤(嵌套字段,格式见上文) | |
| 159 | 204 | |
| 160 | 205 | #### 2. 范围过滤器 (range_filters) |
| 161 | 206 | |
| ... | ... | @@ -201,7 +246,7 @@ curl -X POST "http://120.76.41.98:6002/search/" \ |
| 201 | 246 | **简单模式**(字符串数组): |
| 202 | 247 | ```json |
| 203 | 248 | { |
| 204 | - "facets": ["category.keyword", "vendor.keyword"] | |
| 249 | + "facets": ["category1_name", "category2_name", "category3_name", "specifications"] | |
| 205 | 250 | } |
| 206 | 251 | ``` |
| 207 | 252 | |
| ... | ... | @@ -210,7 +255,7 @@ curl -X POST "http://120.76.41.98:6002/search/" \ |
| 210 | 255 | { |
| 211 | 256 | "facets": [ |
| 212 | 257 | { |
| 213 | - "field": "category.keyword", | |
| 258 | + "field": "category1_name", | |
| 214 | 259 | "size": 15, |
| 215 | 260 | "type": "terms" |
| 216 | 261 | }, |
| ... | ... | @@ -223,6 +268,53 @@ curl -X POST "http://120.76.41.98:6002/search/" \ |
| 223 | 268 | {"key": "100-200", "from": 100, "to": 200}, |
| 224 | 269 | {"key": "200+", "from": 200} |
| 225 | 270 | ] |
| 271 | + }, | |
| 272 | + "specifications" // 规格分面(特殊处理:嵌套聚合,按name分组,然后按value聚合) | |
| 273 | + ] | |
| 274 | +} | |
| 275 | +``` | |
| 276 | + | |
| 277 | +**规格分面说明**: | |
| 278 | + | |
| 279 | +`specifications` 是嵌套字段,支持两种分面模式: | |
| 280 | + | |
| 281 | +**模式1:所有规格名称的分面** (`"specifications"`): | |
| 282 | +```json | |
| 283 | +{ | |
| 284 | + "facets": ["specifications"] | |
| 285 | +} | |
| 286 | +``` | |
| 287 | +返回所有规格名称(name)及其对应的值(value)列表。每个 name 会生成一个独立的分面结果。 | |
| 288 | + | |
| 289 | +**模式2:指定规格名称的分面** (`"specifications.color"`): | |
| 290 | +```json | |
| 291 | +{ | |
| 292 | + "facets": ["specifications.color", "specifications.size"] | |
| 293 | +} | |
| 294 | +``` | |
| 295 | +只返回指定规格名称的值列表。格式:`specifications.{name}`,其中 `{name}` 是规格名称(如"color"、"size")。 | |
| 296 | + | |
| 297 | +**返回格式示例**: | |
| 298 | +```json | |
| 299 | +{ | |
| 300 | + "facets": [ | |
| 301 | + { | |
| 302 | + "field": "specifications.color", | |
| 303 | + "label": "color", | |
| 304 | + "type": "terms", | |
| 305 | + "values": [ | |
| 306 | + {"value": "white", "count": 50, "selected": false}, | |
| 307 | + {"value": "black", "count": 30, "selected": false} | |
| 308 | + ] | |
| 309 | + }, | |
| 310 | + { | |
| 311 | + "field": "specifications.size", | |
| 312 | + "label": "size", | |
| 313 | + "type": "terms", | |
| 314 | + "values": [ | |
| 315 | + {"value": "256GB", "count": 40, "selected": false}, | |
| 316 | + {"value": "512GB", "count": 20, "selected": false} | |
| 317 | + ] | |
| 226 | 318 | } |
| 227 | 319 | ] |
| 228 | 320 | } |
| ... | ... | @@ -276,28 +368,47 @@ curl -X POST "http://120.76.41.98:6002/search/" \ |
| 276 | 368 | { |
| 277 | 369 | "spu_id": "12345", |
| 278 | 370 | "title": "芭比时尚娃娃", |
| 279 | - "handle": "barbie-doll", | |
| 280 | - "description": "高品质芭比娃娃", | |
| 371 | + "brief": "高品质芭比娃娃", | |
| 372 | + "description": "详细描述...", | |
| 281 | 373 | "vendor": "美泰", |
| 282 | 374 | "category": "玩具", |
| 283 | - "tags": "娃娃, 玩具, 女孩", | |
| 375 | + "category_path": "玩具/娃娃/时尚", | |
| 376 | + "category_name": "时尚", | |
| 377 | + "category_id": "cat_001", | |
| 378 | + "category_level": 3, | |
| 379 | + "category1_name": "玩具", | |
| 380 | + "category2_name": "娃娃", | |
| 381 | + "category3_name": "时尚", | |
| 382 | + "tags": ["娃娃", "玩具", "女孩"], | |
| 284 | 383 | "price": 89.99, |
| 285 | 384 | "compare_at_price": 129.99, |
| 286 | 385 | "currency": "USD", |
| 287 | 386 | "image_url": "https://example.com/image.jpg", |
| 288 | 387 | "in_stock": true, |
| 388 | + "sku_prices": [89.99, 99.99, 109.99], | |
| 389 | + "sku_weights": [100, 150, 200], | |
| 390 | + "sku_weight_units": ["g", "g", "g"], | |
| 391 | + "total_inventory": 500, | |
| 392 | + "option1_name": "color", | |
| 393 | + "option2_name": "size", | |
| 394 | + "option3_name": null, | |
| 395 | + "specifications": [ | |
| 396 | + {"sku_id": "sku_001", "name": "color", "value": "pink"}, | |
| 397 | + {"sku_id": "sku_001", "name": "size", "value": "standard"} | |
| 398 | + ], | |
| 289 | 399 | "skus": [ |
| 290 | 400 | { |
| 291 | 401 | "sku_id": "67890", |
| 292 | - "title": "粉色款", | |
| 293 | 402 | "price": 89.99, |
| 294 | 403 | "compare_at_price": 129.99, |
| 295 | 404 | "sku": "BARBIE-001", |
| 296 | 405 | "stock": 100, |
| 297 | - "options": { | |
| 298 | - "option1": "粉色", | |
| 299 | - "option2": "标准款" | |
| 300 | - } | |
| 406 | + "weight": 0.1, | |
| 407 | + "weight_unit": "kg", | |
| 408 | + "option1_value": "pink", | |
| 409 | + "option2_value": "standard", | |
| 410 | + "option3_value": null, | |
| 411 | + "image_src": "https://example.com/sku1.jpg" | |
| 301 | 412 | } |
| 302 | 413 | ], |
| 303 | 414 | "relevance_score": 8.5 |
| ... | ... | @@ -307,8 +418,8 @@ curl -X POST "http://120.76.41.98:6002/search/" \ |
| 307 | 418 | "max_score": 8.5, |
| 308 | 419 | "facets": [ |
| 309 | 420 | { |
| 310 | - "field": "category.keyword", | |
| 311 | - "label": "category.keyword", | |
| 421 | + "field": "category1_name", | |
| 422 | + "label": "category1_name", | |
| 312 | 423 | "type": "terms", |
| 313 | 424 | "values": [ |
| 314 | 425 | { |
| ... | ... | @@ -318,6 +429,19 @@ curl -X POST "http://120.76.41.98:6002/search/" \ |
| 318 | 429 | "selected": false |
| 319 | 430 | } |
| 320 | 431 | ] |
| 432 | + }, | |
| 433 | + { | |
| 434 | + "field": "specifications.color", | |
| 435 | + "label": "color", | |
| 436 | + "type": "terms", | |
| 437 | + "values": [ | |
| 438 | + { | |
| 439 | + "value": "pink", | |
| 440 | + "label": "pink", | |
| 441 | + "count": 30, | |
| 442 | + "selected": false | |
| 443 | + } | |
| 444 | + ] | |
| 321 | 445 | } |
| 322 | 446 | ], |
| 323 | 447 | "query_info": { |
| ... | ... | @@ -356,31 +480,55 @@ curl -X POST "http://120.76.41.98:6002/search/" \ |
| 356 | 480 | | 字段 | 类型 | 说明 | |
| 357 | 481 | |------|------|------| |
| 358 | 482 | | `spu_id` | string | SPU ID | |
| 359 | -| `title` | string | 商品标题 | | |
| 360 | -| `handle` | string | 商品URL handle | | |
| 361 | -| `description` | string | 商品描述 | | |
| 362 | -| `vendor` | string | 供应商/品牌 | | |
| 363 | -| `category` | string | 类目 | | |
| 364 | -| `tags` | string | 标签 | | |
| 483 | +| `title` | string | 商品标题(根据language参数自动选择title_zh或title_en) | | |
| 484 | +| `brief` | string | 商品短描述(根据language参数自动选择) | | |
| 485 | +| `description` | string | 商品详细描述(根据language参数自动选择) | | |
| 486 | +| `vendor` | string | 供应商/品牌(根据language参数自动选择) | | |
| 487 | +| `category` | string | 类目(兼容字段,等同于category_name) | | |
| 488 | +| `category_path` | string | 类目路径(多级,用于面包屑,根据language参数自动选择) | | |
| 489 | +| `category_name` | string | 类目名称(展示用,根据language参数自动选择) | | |
| 490 | +| `category_id` | string | 类目ID | | |
| 491 | +| `category_level` | integer | 类目层级(1/2/3) | | |
| 492 | +| `category1_name` | string | 一级类目名称 | | |
| 493 | +| `category2_name` | string | 二级类目名称 | | |
| 494 | +| `category3_name` | string | 三级类目名称 | | |
| 495 | +| `tags` | array[string] | 标签列表 | | |
| 365 | 496 | | `price` | float | 价格(min_price) | |
| 366 | 497 | | `compare_at_price` | float | 原价 | |
| 367 | 498 | | `currency` | string | 货币单位(默认USD) | |
| 368 | 499 | | `image_url` | string | 主图URL | |
| 369 | 500 | | `in_stock` | boolean | 是否有库存(任意SKU有库存即为true) | |
| 501 | +| `sku_prices` | array[float] | 所有SKU价格列表 | | |
| 502 | +| `sku_weights` | array[integer] | 所有SKU重量列表 | | |
| 503 | +| `sku_weight_units` | array[string] | 所有SKU重量单位列表 | | |
| 504 | +| `total_inventory` | integer | 总库存 | | |
| 505 | +| `option1_name` | string | 选项1名称(如"color") | | |
| 506 | +| `option2_name` | string | 选项2名称(如"size") | | |
| 507 | +| `option3_name` | string | 选项3名称 | | |
| 508 | +| `specifications` | array[object] | 规格列表(与ES specifications字段对应) | | |
| 370 | 509 | | `skus` | array | SKU 列表 | |
| 371 | 510 | | `relevance_score` | float | 相关性分数 | |
| 372 | 511 | |
| 512 | +**多语言字段说明**: | |
| 513 | +- `title`, `brief`, `description`, `vendor`, `category_path`, `category_name` 会根据请求的 `language` 参数自动选择对应的中英文字段 | |
| 514 | +- `language="zh"`: 优先返回 `*_zh` 字段,如果为空则回退到 `*_en` 字段 | |
| 515 | +- `language="en"`: 优先返回 `*_en` 字段,如果为空则回退到 `*_zh` 字段 | |
| 516 | + | |
| 373 | 517 | ### SkuResult字段说明 |
| 374 | 518 | |
| 375 | 519 | | 字段 | 类型 | 说明 | |
| 376 | 520 | |------|------|------| |
| 377 | 521 | | `sku_id` | string | SKU ID | |
| 378 | -| `title` | string | SKU标题 | | |
| 379 | 522 | | `price` | float | 价格 | |
| 380 | 523 | | `compare_at_price` | float | 原价 | |
| 381 | -| `sku` | string | SKU编码 | | |
| 524 | +| `sku` | string | SKU编码(sku_code) | | |
| 382 | 525 | | `stock` | integer | 库存数量 | |
| 383 | -| `options` | object | 选项(颜色、尺寸等) | | |
| 526 | +| `weight` | float | 重量 | | |
| 527 | +| `weight_unit` | string | 重量单位 | | |
| 528 | +| `option1_value` | string | 选项1取值(如color值) | | |
| 529 | +| `option2_value` | string | 选项2取值(如size值) | | |
| 530 | +| `option3_value` | string | 选项3取值 | | |
| 531 | +| `image_src` | string | SKU图片地址 | | |
| 384 | 532 | |
| 385 | 533 | --- |
| 386 | 534 | |
| ... | ... | @@ -408,8 +556,9 @@ curl -X POST "http://120.76.41.98:6002/search/" \ |
| 408 | 556 | { |
| 409 | 557 | "query": "玩具", |
| 410 | 558 | "size": 20, |
| 559 | + "language": "zh", | |
| 411 | 560 | "filters": { |
| 412 | - "category.keyword": "益智玩具" | |
| 561 | + "category_name": "益智玩具" | |
| 413 | 562 | }, |
| 414 | 563 | "range_filters": { |
| 415 | 564 | "min_price": { |
| ... | ... | @@ -428,23 +577,26 @@ curl -X POST "http://120.76.41.98:6002/search/" \ |
| 428 | 577 | { |
| 429 | 578 | "query": "玩具", |
| 430 | 579 | "size": 20, |
| 580 | + "language": "zh", | |
| 431 | 581 | "facets": [ |
| 432 | - "category.keyword", | |
| 433 | - "vendor.keyword" | |
| 582 | + "category1_name", | |
| 583 | + "category2_name", | |
| 584 | + "specifications" | |
| 434 | 585 | ] |
| 435 | 586 | } |
| 436 | 587 | ``` |
| 437 | 588 | |
| 438 | 589 | ### 场景4:多条件组合搜索 |
| 439 | 590 | |
| 440 | -**需求**: 搜索"玩具",筛选多个品牌,价格范围,并获取分面统计 | |
| 591 | +**需求**: 搜索"手机",筛选多个品牌,价格范围,并获取分面统计 | |
| 441 | 592 | |
| 442 | 593 | ```json |
| 443 | 594 | { |
| 444 | - "query": "玩具", | |
| 595 | + "query": "手机", | |
| 445 | 596 | "size": 20, |
| 597 | + "language": "zh", | |
| 446 | 598 | "filters": { |
| 447 | - "vendor.keyword": ["乐高", "孩之宝", "美泰"] | |
| 599 | + "vendor_zh.keyword": ["品牌A", "品牌B"] | |
| 448 | 600 | }, |
| 449 | 601 | "range_filters": { |
| 450 | 602 | "min_price": { |
| ... | ... | @@ -454,7 +606,7 @@ curl -X POST "http://120.76.41.98:6002/search/" \ |
| 454 | 606 | }, |
| 455 | 607 | "facets": [ |
| 456 | 608 | { |
| 457 | - "field": "category.keyword", | |
| 609 | + "field": "category1_name", | |
| 458 | 610 | "size": 15 |
| 459 | 611 | }, |
| 460 | 612 | { |
| ... | ... | @@ -466,31 +618,117 @@ curl -X POST "http://120.76.41.98:6002/search/" \ |
| 466 | 618 | {"key": "100-200", "from": 100, "to": 200}, |
| 467 | 619 | {"key": "200+", "from": 200} |
| 468 | 620 | ] |
| 469 | - } | |
| 621 | + }, | |
| 622 | + "specifications" | |
| 470 | 623 | ], |
| 471 | 624 | "sort_by": "min_price", |
| 472 | 625 | "sort_order": "asc" |
| 473 | 626 | } |
| 474 | 627 | ``` |
| 475 | 628 | |
| 476 | -### 场景5:布尔表达式搜索 | |
| 629 | +### 场景5:规格过滤搜索 | |
| 630 | + | |
| 631 | +**需求**: 搜索"手机",筛选color为"white"的商品 | |
| 632 | + | |
| 633 | +```json | |
| 634 | +{ | |
| 635 | + "query": "手机", | |
| 636 | + "size": 20, | |
| 637 | + "language": "zh", | |
| 638 | + "filters": { | |
| 639 | + "specifications": { | |
| 640 | + "name": "color", | |
| 641 | + "value": "white" | |
| 642 | + } | |
| 643 | + } | |
| 644 | +} | |
| 645 | +``` | |
| 646 | + | |
| 647 | +### 场景6:多个规格过滤(OR逻辑) | |
| 648 | + | |
| 649 | +**需求**: 搜索"手机",筛选color为"white"或size为"256GB"的商品 | |
| 650 | + | |
| 651 | +```json | |
| 652 | +{ | |
| 653 | + "query": "手机", | |
| 654 | + "size": 20, | |
| 655 | + "language": "zh", | |
| 656 | + "filters": { | |
| 657 | + "specifications": [ | |
| 658 | + {"name": "color", "value": "white"}, | |
| 659 | + {"name": "size", "value": "256GB"} | |
| 660 | + ] | |
| 661 | + } | |
| 662 | +} | |
| 663 | +``` | |
| 664 | + | |
| 665 | +### 场景7:规格分面搜索 | |
| 666 | + | |
| 667 | +**需求**: 搜索"手机",获取所有规格的分面统计 | |
| 668 | + | |
| 669 | +```json | |
| 670 | +{ | |
| 671 | + "query": "手机", | |
| 672 | + "size": 20, | |
| 673 | + "language": "zh", | |
| 674 | + "facets": ["specifications"] | |
| 675 | +} | |
| 676 | +``` | |
| 677 | + | |
| 678 | +**需求**: 只获取"color"规格的分面统计 | |
| 679 | + | |
| 680 | +```json | |
| 681 | +{ | |
| 682 | + "query": "手机", | |
| 683 | + "size": 20, | |
| 684 | + "language": "zh", | |
| 685 | + "facets": ["specifications.color", "specifications.size"] | |
| 686 | +} | |
| 687 | +``` | |
| 688 | + | |
| 689 | +### 场景8:组合过滤和分面 | |
| 690 | + | |
| 691 | +**需求**: 搜索"手机",筛选类目和规格,并获取对应的分面统计 | |
| 692 | + | |
| 693 | +```json | |
| 694 | +{ | |
| 695 | + "query": "手机", | |
| 696 | + "size": 20, | |
| 697 | + "language": "zh", | |
| 698 | + "filters": { | |
| 699 | + "category_name": "手机", | |
| 700 | + "specifications": { | |
| 701 | + "name": "color", | |
| 702 | + "value": "white" | |
| 703 | + } | |
| 704 | + }, | |
| 705 | + "facets": [ | |
| 706 | + "category1_name", | |
| 707 | + "category2_name", | |
| 708 | + "specifications.color", | |
| 709 | + "specifications.size" | |
| 710 | + ] | |
| 711 | +} | |
| 712 | +``` | |
| 713 | + | |
| 714 | +### 场景9:布尔表达式搜索 | |
| 477 | 715 | |
| 478 | -**需求**: 搜索包含"玩具"和"乐高"的商品,排除"电动" | |
| 716 | +**需求**: 搜索包含"手机"和"智能"的商品,排除"二手" | |
| 479 | 717 | |
| 480 | 718 | ```json |
| 481 | 719 | { |
| 482 | - "query": "玩具 AND 乐高 ANDNOT 电动", | |
| 720 | + "query": "手机 AND 智能 ANDNOT 二手", | |
| 483 | 721 | "size": 20 |
| 484 | 722 | } |
| 485 | 723 | ``` |
| 486 | 724 | |
| 487 | -### 场景6:分页查询 | |
| 725 | +### 场景10:分页查询 | |
| 488 | 726 | |
| 489 | 727 | **需求**: 获取第2页结果(每页20条) |
| 490 | 728 | |
| 491 | 729 | ```json |
| 492 | 730 | { |
| 493 | - "query": "玩具", | |
| 731 | + "query": "手机", | |
| 494 | 732 | "size": 20, |
| 495 | 733 | "from": 20 |
| 496 | 734 | } |
| ... | ... | @@ -650,25 +888,33 @@ curl "http://localhost:6002/search/12345" |
| 650 | 888 | |
| 651 | 889 | | 字段名 | 类型 | 描述 | |
| 652 | 890 | |--------|------|------| |
| 891 | +| `tenant_id` | keyword | 租户ID(多租户隔离) | | |
| 653 | 892 | | `spu_id` | keyword | SPU ID | |
| 654 | -| `sku_id` | keyword/long | SKU ID(主键) | | |
| 655 | -| `title` | text | 商品名称(中文) | | |
| 656 | -| `en_title` | text | 商品名称(英文) | | |
| 657 | -| `ru_title` | text | 商品名称(俄文) | | |
| 658 | -| `category.keyword` | keyword | 类目(精确匹配) | | |
| 659 | -| `vendor.keyword` | keyword | 品牌/供应商(精确匹配) | | |
| 660 | -| `category` | HKText | 类目(支持 `category.keyword` 精确匹配) | | |
| 661 | -| `tags.keyword` | keyword | 标签 | | |
| 662 | -| `min_price` | double | 最低价格 | | |
| 663 | -| `max_price` | double | 最高价格 | | |
| 664 | -| `compare_at_price` | double | 原价 | | |
| 665 | -| `create_time` | date | 创建时间 | | |
| 666 | -| `update_time` | date | 更新时间 | | |
| 667 | -| `in_stock` | boolean | 是否有库存 | | |
| 668 | -| `text_embedding` | dense_vector | 文本向量(1024 维) | | |
| 669 | -| `image_embedding` | dense_vector | 图片向量(1024 维) | | |
| 670 | - | |
| 671 | -> 不同租户可自定义字段名称。推荐将可过滤的文本字段配置为 HKText,这样即可同时支持全文检索和 `field.keyword` 精确过滤;数值字段单独建索引以用于排序/Range。 | |
| 893 | +| `title_zh`, `title_en` | text | 商品标题(中英文) | | |
| 894 | +| `brief_zh`, `brief_en` | text | 商品短描述(中英文) | | |
| 895 | +| `description_zh`, `description_en` | text | 商品详细描述(中英文) | | |
| 896 | +| `vendor_zh`, `vendor_en` | text | 供应商/品牌(中英文,含keyword子字段) | | |
| 897 | +| `category_path_zh`, `category_path_en` | text | 类目路径(中英文,用于搜索) | | |
| 898 | +| `category_name_zh`, `category_name_en` | text | 类目名称(中英文,用于搜索) | | |
| 899 | +| `category_id` | keyword | 类目ID | | |
| 900 | +| `category_name` | keyword | 类目名称(用于过滤) | | |
| 901 | +| `category_level` | integer | 类目层级 | | |
| 902 | +| `category1_name`, `category2_name`, `category3_name` | keyword | 多级类目名称(用于过滤和分面) | | |
| 903 | +| `tags` | keyword | 标签(数组) | | |
| 904 | +| `specifications` | nested | 规格(嵌套对象数组) | | |
| 905 | +| `option1_name`, `option2_name`, `option3_name` | keyword | 选项名称 | | |
| 906 | +| `min_price`, `max_price` | float | 最低/最高价格 | | |
| 907 | +| `compare_at_price` | float | 原价 | | |
| 908 | +| `sku_prices` | float | SKU价格列表(数组) | | |
| 909 | +| `sku_weights` | long | SKU重量列表(数组) | | |
| 910 | +| `sku_weight_units` | keyword | SKU重量单位列表(数组) | | |
| 911 | +| `total_inventory` | long | 总库存 | | |
| 912 | +| `skus` | nested | SKU详细信息(嵌套对象数组) | | |
| 913 | +| `create_time`, `update_time` | date | 创建/更新时间 | | |
| 914 | +| `title_embedding` | dense_vector | 标题向量(1024维,仅用于搜索) | | |
| 915 | +| `image_embedding` | nested | 图片向量(嵌套,仅用于搜索) | | |
| 916 | + | |
| 917 | +> 所有租户共享统一的索引结构。文本字段支持中英文双语,后端根据 `language` 参数自动选择对应字段返回。 | |
| 672 | 918 | |
| 673 | 919 | --- |
| 674 | 920 | |
| ... | ... | @@ -676,11 +922,14 @@ curl "http://localhost:6002/search/12345" |
| 676 | 922 | |
| 677 | 923 | ### 常用字段列表 |
| 678 | 924 | |
| 679 | -#### 过滤字段(使用 HKText 的 keyword 子字段) | |
| 925 | +#### 过滤字段 | |
| 680 | 926 | |
| 681 | -- `category.keyword`: 类目 | |
| 682 | -- `vendor.keyword`: 品牌/供应商 | |
| 683 | -- `tags.keyword`: 标签 | |
| 927 | +- `category_name`: 类目名称 | |
| 928 | +- `category1_name`, `category2_name`, `category3_name`: 多级类目 | |
| 929 | +- `category_id`: 类目ID | |
| 930 | +- `vendor_zh.keyword`, `vendor_en.keyword`: 供应商/品牌(使用keyword子字段) | |
| 931 | +- `tags`: 标签(keyword类型) | |
| 932 | +- `option1_name`, `option2_name`, `option3_name`: 选项名称 | |
| 684 | 933 | |
| 685 | 934 | #### 范围字段 |
| 686 | 935 | |
| ... | ... | @@ -694,7 +943,6 @@ curl "http://localhost:6002/search/12345" |
| 694 | 943 | |
| 695 | 944 | - `min_price`: 最低价格 |
| 696 | 945 | - `max_price`: 最高价格 |
| 697 | -- `title`: 标题(字母序) | |
| 698 | 946 | - `create_time`: 创建时间 |
| 699 | 947 | - `update_time`: 更新时间 |
| 700 | 948 | - `relevance_score`: 相关性分数(默认) |
| ... | ... | @@ -703,23 +951,21 @@ curl "http://localhost:6002/search/12345" |
| 703 | 951 | |
| 704 | 952 | | 分析器 | 语言 | 描述 | |
| 705 | 953 | |--------|------|------| |
| 706 | -| `chinese_ecommerce` | 中文 | 基于 Ansj 的电商优化中文分析器 | | |
| 707 | -| `english` | 英文 | 标准英文分析器 | | |
| 708 | -| `russian` | 俄文 | 俄文分析器 | | |
| 709 | -| `arabic` | 阿拉伯文 | 阿拉伯文分析器 | | |
| 710 | -| `spanish` | 西班牙文 | 西班牙文分析器 | | |
| 711 | -| `japanese` | 日文 | 日文分析器 | | |
| 954 | +| `hanlp_index` | 中文 | 中文索引分析器(用于中文字段) | | |
| 955 | +| `hanlp_standard` | 中文 | 中文查询分析器(用于中文字段) | | |
| 956 | +| `english` | 英文 | 标准英文分析器(用于英文字段) | | |
| 957 | +| `lowercase` | - | 小写标准化器(用于keyword子字段) | | |
| 712 | 958 | |
| 713 | 959 | ### 字段类型速查 |
| 714 | 960 | |
| 715 | 961 | | 类型 | ES Mapping | 用途 | |
| 716 | 962 | |------|------------|------| |
| 717 | -| `TEXT` | `text` | 全文检索 | | |
| 718 | -| `KEYWORD` | `keyword` | 精确匹配、聚合、排序 | | |
| 719 | -| `LONG` | `long` | 整数 | | |
| 720 | -| `DOUBLE` | `double` | 浮点数 | | |
| 721 | -| `DATE` | `date` | 日期时间 | | |
| 722 | -| `BOOLEAN` | `boolean` | 布尔值 | | |
| 723 | -| `TEXT_EMBEDDING` | `dense_vector` | 文本语义向量 | | |
| 724 | -| `IMAGE_EMBEDDING` | `dense_vector` | 图片语义向量 | | |
| 963 | +| `text` | `text` | 全文检索(支持中英文分析器) | | |
| 964 | +| `keyword` | `keyword` | 精确匹配、聚合、排序 | | |
| 965 | +| `integer` | `integer` | 整数 | | |
| 966 | +| `long` | `long` | 长整数 | | |
| 967 | +| `float` | `float` | 浮点数 | | |
| 968 | +| `date` | `date` | 日期时间 | | |
| 969 | +| `nested` | `nested` | 嵌套对象(specifications, skus, image_embedding) | | |
| 970 | +| `dense_vector` | `dense_vector` | 向量字段(title_embedding,仅用于搜索) | | |
| 725 | 971 | ... | ... |
docs/搜索API速查表.md
| ... | ... | @@ -17,8 +17,38 @@ POST /search/ |
| 17 | 17 | ```bash |
| 18 | 18 | { |
| 19 | 19 | "filters": { |
| 20 | - "category.keyword": "玩具", // 单值 | |
| 21 | - "vendor.keyword": ["乐高", "美泰"] // 多值(OR) | |
| 20 | + "category_name": "手机", // 单值 | |
| 21 | + "category1_name": "服装", // 一级类目 | |
| 22 | + "vendor_zh.keyword": ["奇乐", "品牌A"], // 多值(OR) | |
| 23 | + "tags": "手机", // 标签 | |
| 24 | + // specifications 嵌套过滤 | |
| 25 | + "specifications": { | |
| 26 | + "name": "color", | |
| 27 | + "value": "white" | |
| 28 | + } | |
| 29 | + } | |
| 30 | +} | |
| 31 | +``` | |
| 32 | + | |
| 33 | +### Specifications 过滤 | |
| 34 | + | |
| 35 | +**单个规格**: | |
| 36 | +```bash | |
| 37 | +{ | |
| 38 | + "filters": { | |
| 39 | + "specifications": {"name": "color", "value": "white"} | |
| 40 | + } | |
| 41 | +} | |
| 42 | +``` | |
| 43 | + | |
| 44 | +**多个规格(OR)**: | |
| 45 | +```bash | |
| 46 | +{ | |
| 47 | + "filters": { | |
| 48 | + "specifications": [ | |
| 49 | + {"name": "color", "value": "white"}, | |
| 50 | + {"name": "size", "value": "256GB"} | |
| 51 | + ] | |
| 22 | 52 | } |
| 23 | 53 | } |
| 24 | 54 | ``` |
| ... | ... | @@ -48,7 +78,23 @@ POST /search/ |
| 48 | 78 | |
| 49 | 79 | ```bash |
| 50 | 80 | { |
| 51 | - "facets": ["category.keyword", "vendor.keyword"] | |
| 81 | + "facets": ["category1_name", "category2_name", "category3_name", "specifications"] | |
| 82 | +} | |
| 83 | +``` | |
| 84 | + | |
| 85 | +### Specifications 分面 | |
| 86 | + | |
| 87 | +**所有规格名称**: | |
| 88 | +```bash | |
| 89 | +{ | |
| 90 | + "facets": ["specifications"] // 返回所有name及其value列表 | |
| 91 | +} | |
| 92 | +``` | |
| 93 | + | |
| 94 | +**指定规格名称**: | |
| 95 | +```bash | |
| 96 | +{ | |
| 97 | + "facets": ["specifications.color", "specifications.size"] // 只返回指定name的value列表 | |
| 52 | 98 | } |
| 53 | 99 | ``` |
| 54 | 100 | |
| ... | ... | @@ -57,15 +103,18 @@ POST /search/ |
| 57 | 103 | ```bash |
| 58 | 104 | { |
| 59 | 105 | "facets": [ |
| 60 | - {"field": "category.keyword", "size": 15}, | |
| 106 | + {"field": "category1_name", "size": 15}, | |
| 61 | 107 | { |
| 62 | - "field": "price", | |
| 108 | + "field": "min_price", | |
| 63 | 109 | "type": "range", |
| 64 | 110 | "ranges": [ |
| 65 | 111 | {"key": "0-50", "to": 50}, |
| 66 | 112 | {"key": "50-100", "from": 50, "to": 100} |
| 67 | 113 | ] |
| 68 | - } | |
| 114 | + }, | |
| 115 | + "specifications", // 所有规格名称 | |
| 116 | + "specifications.color", // 指定规格名称 | |
| 117 | + "specifications.size" | |
| 69 | 118 | ] |
| 70 | 119 | } |
| 71 | 120 | ``` |
| ... | ... | @@ -110,19 +159,25 @@ POST /search/ |
| 110 | 159 | |
| 111 | 160 | ```bash |
| 112 | 161 | POST /search/ |
| 162 | +Headers: X-Tenant-ID: 2 | |
| 113 | 163 | { |
| 114 | - "query": "玩具", | |
| 164 | + "query": "手机", | |
| 115 | 165 | "size": 20, |
| 116 | 166 | "from": 0, |
| 167 | + "language": "zh", | |
| 117 | 168 | "filters": { |
| 118 | - "category.keyword": ["玩具", "益智玩具"] | |
| 169 | + "category_name": "手机", | |
| 170 | + "category1_name": "电子产品", | |
| 171 | + "specifications": {"name": "color", "value": "white"} | |
| 119 | 172 | }, |
| 120 | 173 | "range_filters": { |
| 121 | - "price": {"gte": 50, "lte": 200} | |
| 174 | + "min_price": {"gte": 50, "lte": 200} | |
| 122 | 175 | }, |
| 123 | 176 | "facets": [ |
| 124 | - {"field": "vendor.keyword", "size": 15}, | |
| 125 | - {"field": "category.keyword", "size": 15} | |
| 177 | + {"field": "category1_name", "size": 15}, | |
| 178 | + {"field": "category2_name", "size": 15}, | |
| 179 | + "specifications.color", | |
| 180 | + "specifications.size" | |
| 126 | 181 | ], |
| 127 | 182 | "sort_by": "min_price", |
| 128 | 183 | "sort_order": "asc" |
| ... | ... | @@ -135,11 +190,29 @@ POST /search/ |
| 135 | 190 | |
| 136 | 191 | ```json |
| 137 | 192 | { |
| 138 | - "hits": [ | |
| 193 | + "results": [ | |
| 139 | 194 | { |
| 140 | - "_id": "12345", | |
| 141 | - "_score": 8.5, | |
| 142 | - "_source": {...} | |
| 195 | + "spu_id": "12345", | |
| 196 | + "title": "商品标题", | |
| 197 | + "brief": "短描述", | |
| 198 | + "description": "详细描述", | |
| 199 | + "vendor": "供应商", | |
| 200 | + "category": "类目", | |
| 201 | + "category_path": "类目/路径", | |
| 202 | + "category_name": "类目名称", | |
| 203 | + "category1_name": "一级类目", | |
| 204 | + "category2_name": "二级类目", | |
| 205 | + "category3_name": "三级类目", | |
| 206 | + "tags": ["标签1", "标签2"], | |
| 207 | + "price": 99.99, | |
| 208 | + "compare_at_price": 149.99, | |
| 209 | + "sku_prices": [99.99, 109.99], | |
| 210 | + "total_inventory": 500, | |
| 211 | + "specifications": [ | |
| 212 | + {"sku_id": "sku_001", "name": "color", "value": "white"} | |
| 213 | + ], | |
| 214 | + "skus": [...], | |
| 215 | + "relevance_score": 8.5 | |
| 143 | 216 | } |
| 144 | 217 | ], |
| 145 | 218 | "total": 118, |
| ... | ... | @@ -147,17 +220,30 @@ POST /search/ |
| 147 | 220 | "took_ms": 45, |
| 148 | 221 | "facets": [ |
| 149 | 222 | { |
| 150 | - "field": "category.keyword", | |
| 151 | - "label": "商品类目", | |
| 223 | + "field": "category1_name", | |
| 224 | + "label": "category1_name", | |
| 152 | 225 | "type": "terms", |
| 153 | 226 | "values": [ |
| 154 | 227 | { |
| 155 | - "value": "玩具", | |
| 156 | - "label": "玩具", | |
| 228 | + "value": "手机", | |
| 229 | + "label": "手机", | |
| 157 | 230 | "count": 85, |
| 158 | 231 | "selected": false |
| 159 | 232 | } |
| 160 | 233 | ] |
| 234 | + }, | |
| 235 | + { | |
| 236 | + "field": "specifications.color", | |
| 237 | + "label": "color", | |
| 238 | + "type": "terms", | |
| 239 | + "values": [ | |
| 240 | + { | |
| 241 | + "value": "white", | |
| 242 | + "label": "white", | |
| 243 | + "count": 30, | |
| 244 | + "selected": false | |
| 245 | + } | |
| 246 | + ] | |
| 161 | 247 | } |
| 162 | 248 | ] |
| 163 | 249 | } |
| ... | ... | @@ -192,14 +278,19 @@ GET /admin/stats |
| 192 | 278 | ```python |
| 193 | 279 | import requests |
| 194 | 280 | |
| 195 | -result = requests.post('http://localhost:6002/search/', json={ | |
| 196 | - "query": "玩具", | |
| 197 | - "filters": {"category.keyword": "玩具"}, | |
| 198 | - "range_filters": {"price": {"gte": 50, "lte": 200}}, | |
| 199 | - "facets": ["vendor.keyword"], | |
| 200 | - "sort_by": "min_price", | |
| 201 | - "sort_order": "asc" | |
| 202 | -}).json() | |
| 281 | +result = requests.post( | |
| 282 | + 'http://localhost:6002/search/', | |
| 283 | + headers={'X-Tenant-ID': '2'}, | |
| 284 | + json={ | |
| 285 | + "query": "手机", | |
| 286 | + "language": "zh", | |
| 287 | + "filters": {"category_name": "手机"}, | |
| 288 | + "range_filters": {"min_price": {"gte": 50, "lte": 200}}, | |
| 289 | + "facets": ["category1_name", "specifications"], | |
| 290 | + "sort_by": "min_price", | |
| 291 | + "sort_order": "asc" | |
| 292 | + } | |
| 293 | +).json() | |
| 203 | 294 | |
| 204 | 295 | print(f"找到 {result['total']} 个结果") |
| 205 | 296 | ``` |
| ... | ... | @@ -211,12 +302,16 @@ print(f"找到 {result['total']} 个结果") |
| 211 | 302 | ```javascript |
| 212 | 303 | const result = await fetch('http://localhost:6002/search/', { |
| 213 | 304 | method: 'POST', |
| 214 | - headers: {'Content-Type': 'application/json'}, | |
| 305 | + headers: { | |
| 306 | + 'Content-Type': 'application/json', | |
| 307 | + 'X-Tenant-ID': '2' | |
| 308 | + }, | |
| 215 | 309 | body: JSON.stringify({ |
| 216 | - query: "玩具", | |
| 217 | - filters: {category.keyword: "玩具"}, | |
| 218 | - range_filters: {price: {gte: 50, lte: 200}}, | |
| 219 | - facets: ["vendor.keyword"], | |
| 310 | + query: "手机", | |
| 311 | + language: "zh", | |
| 312 | + filters: {category_name: "手机"}, | |
| 313 | + range_filters: {min_price: {gte: 50, lte: 200}}, | |
| 314 | + facets: ["category1_name", "specifications"], | |
| 220 | 315 | sort_by: "min_price", |
| 221 | 316 | sort_order: "asc" |
| 222 | 317 | }) | ... | ... |
docs/系统设计文档.md
| ... | ... | @@ -215,7 +215,7 @@ indexes: |
| 215 | 215 | 4. 组合多个语言查询的结果,提高召回率 |
| 216 | 216 | |
| 217 | 217 | **实现模块**: |
| 218 | -- `search/multilang_query_builder.py` - 多语言查询构建器 | |
| 218 | +- `search/es_query_builder.py` - ES 查询构建器(单层架构) | |
| 219 | 219 | - `query/query_parser.py` - 查询解析器(支持语言检测和翻译) |
| 220 | 220 | |
| 221 | 221 | --- |
| ... | ... | @@ -345,7 +345,7 @@ query_config: |
| 345 | 345 | |
| 346 | 346 | #### 工作流程 |
| 347 | 347 | ``` |
| 348 | -查询输入 → 语言检测 → 确定目标语言 → 翻译 → 多语言查询构建 | |
| 348 | +查询输入 → 语言检测 → 翻译 → 查询构建(filters and (text_recall or embedding_recall)) | |
| 349 | 349 | ``` |
| 350 | 350 | |
| 351 | 351 | #### 实现模块 |
| ... | ... | @@ -421,31 +421,57 @@ laptop AND (gaming OR professional) ANDNOT cheap |
| 421 | 421 | - 提取域(如 `title:查询` → 域=`title`,查询=`查询`) |
| 422 | 422 | - 检测查询语言 |
| 423 | 423 | - 生成翻译 |
| 424 | -2. **多语言查询构建**: | |
| 425 | - - 如果域有 `language_field_mapping`: | |
| 426 | - - 使用检测到的语言查询对应字段(boost * 1.5) | |
| 427 | - - 使用翻译后的查询搜索其他语言字段(boost * 1.0) | |
| 428 | - - 如果域没有 `language_field_mapping`: | |
| 429 | - - 使用所有字段进行搜索 | |
| 430 | -3. **查询组合**: | |
| 431 | - - 多个语言查询组合为 `should` 子句 | |
| 432 | - - 提高召回率 | |
| 433 | - | |
| 434 | -#### 示例 | |
| 435 | -``` | |
| 436 | -查询: "芭比娃娃" | |
| 437 | -域: default | |
| 438 | -检测语言: zh | |
| 439 | - | |
| 440 | -生成的查询: | |
| 441 | -- 中文查询 "芭比娃娃" → 搜索 name, categoryName, brandName (boost * 1.5) | |
| 442 | -- 英文翻译 "Barbie doll" → 搜索 enSpuName (boost * 1.0) | |
| 443 | -- 俄文翻译 "Кукла Барби" → 搜索 ruSkuName (boost * 1.0) | |
| 424 | +2. **查询构建**(简化架构): | |
| 425 | + - **结构**: `filters AND (text_recall OR embedding_recall)` | |
| 426 | + - **filters**: 前端传递的过滤条件(永远起作用,放在 `filter` 中) | |
| 427 | + - **text_recall**: 文本相关性召回 | |
| 428 | + - 同时搜索中英文字段(`title_zh/en`, `brief_zh/en`, `description_zh/en`, `vendor_zh/en`, `category_path_zh/en`, `category_name_zh/en`, `tags`) | |
| 429 | + - 使用 `multi_match` 查询,支持字段 boost | |
| 430 | + - **embedding_recall**: 向量召回(KNN) | |
| 431 | + - 使用 `title_embedding` 字段进行 KNN 搜索 | |
| 432 | + - ES 自动与文本召回合并 | |
| 433 | + - **function_score**: 包装召回部分,支持提权字段(新鲜度、销量等) | |
| 434 | + | |
| 435 | +#### 查询结构示例 | |
| 436 | +```json | |
| 437 | +{ | |
| 438 | + "query": { | |
| 439 | + "bool": { | |
| 440 | + "must": [ | |
| 441 | + { | |
| 442 | + "function_score": { | |
| 443 | + "query": { | |
| 444 | + "multi_match": { | |
| 445 | + "query": "手机", | |
| 446 | + "fields": [ | |
| 447 | + "title_zh^3.0", "title_en^3.0", | |
| 448 | + "brief_zh^1.5", "brief_en^1.5", | |
| 449 | + ... | |
| 450 | + ] | |
| 451 | + } | |
| 452 | + }, | |
| 453 | + "functions": [...] | |
| 454 | + } | |
| 455 | + } | |
| 456 | + ], | |
| 457 | + "filter": [ | |
| 458 | + {"term": {"tenant_id": "2"}}, | |
| 459 | + {"term": {"category_name": "手机"}} | |
| 460 | + ] | |
| 461 | + } | |
| 462 | + }, | |
| 463 | + "knn": { | |
| 464 | + "field": "title_embedding", | |
| 465 | + "query_vector": [...], | |
| 466 | + "k": 50, | |
| 467 | + "boost": 0.2 | |
| 468 | + } | |
| 469 | +} | |
| 444 | 470 | ``` |
| 445 | 471 | |
| 446 | 472 | #### 实现模块 |
| 447 | -- `search/multilang_query_builder.py` - 多语言查询构建器 | |
| 448 | -- `search/searcher.py` - 搜索器(使用多语言构建器) | |
| 473 | +- `search/es_query_builder.py` - ES 查询构建器(单层架构,`build_query` 方法) | |
| 474 | +- `search/searcher.py` - 搜索器(使用 `ESQueryBuilder`) | |
| 449 | 475 | |
| 450 | 476 | ### 5.3 相关性计算(Ranking) |
| 451 | 477 | ... | ... |
docs/索引字段说明v2.md
| 1 | -SPU-SKU索引方案选型 | |
| 2 | -1. spu为单位。SKU字段展开作为SPU属性 | |
| 3 | -1.1 索引方案 | |
| 4 | -除了title, brielf description seo相关 cate tags vendor所有影响相关性的字段都在spu。 sku只有款式、价格、重量、库存等相关属性。所以,可以以spu为单位建立索引。 | |
| 5 | -sku中需要参与搜索的属性(比如价格、库存)展开到spu。 | |
| 6 | -sku的所有需要返回的字段作为nested字段,仅用于返回。 | |
| 7 | -# 写入 spu 级别索引 | |
| 8 | -def build_product_document(product, variants): | |
| 9 | - return { | |
| 10 | - "spu_id": str(product.id), | |
| 11 | - "title": product.title, | |
| 12 | - | |
| 13 | - # Variant搜索字段(展开) | |
| 14 | - # 价格(int)、重量(int)、重量单位拼接重量(keyword),都以list形式灌入 | |
| 15 | - # TODO 按要求补充 | |
| 16 | - | |
| 17 | - # 库存总和 将sku的库存加起来作为一个值灌入 | |
| 18 | - # 售价,灌入3个字段,一个 sku价格 以list形式灌入,一个最高价一个最低价 | |
| 19 | - # TODO 按要求补充 | |
| 20 | - | |
| 21 | - # Variant详细信息(用于返回) | |
| 22 | - "variants": [ | |
| 23 | - { | |
| 24 | - "sku_id": str(v.id), | |
| 25 | - "price": float(v.price), | |
| 26 | - "options": v.options | |
| 27 | - } | |
| 28 | - for v in variants | |
| 29 | - ], | |
| 1 | +# 索引字段说明 v2 | |
| 2 | + | |
| 3 | +本文档详细说明 `search_products` 索引的字段结构、类型、数据来源和用途。 | |
| 4 | + | |
| 5 | +## 索引概述 | |
| 6 | + | |
| 7 | +- **索引名称**: `search_products` | |
| 8 | +- **索引维度**: SPU(Standard Product Unit)级别 | |
| 9 | +- **多租户隔离**: 通过 `tenant_id` 字段实现 | |
| 10 | +- **Mapping 文件**: `mappings/search_products.json` | |
| 11 | + | |
| 12 | +## 字段分类 | |
| 13 | + | |
| 14 | +### 1. 基础标识字段 | |
| 15 | + | |
| 16 | +| 字段名 | ES类型 | 说明 | 数据来源 | | |
| 17 | +|--------|--------|------|----------| | |
| 18 | +| `tenant_id` | keyword | 租户ID,用于多租户隔离 | MySQL: `shoplazza_product_spu.tenant_id` | | |
| 19 | +| `spu_id` | keyword | SPU唯一标识 | MySQL: `shoplazza_product_spu.id` | | |
| 20 | +| `create_time` | date | 创建时间 | MySQL: `shoplazza_product_spu.created_at` | | |
| 21 | +| `update_time` | date | 更新时间 | MySQL: `shoplazza_product_spu.updated_at` | | |
| 22 | + | |
| 23 | +### 2. 多语言文本字段 | |
| 24 | + | |
| 25 | +所有文本字段都支持中英文双语,后端根据请求的 `language` 参数自动选择对应语言字段返回。 | |
| 26 | + | |
| 27 | +#### 2.1 标题字段 | |
| 28 | + | |
| 29 | +| 字段名 | ES类型 | 分析器 | 说明 | 数据来源 | | |
| 30 | +|--------|--------|--------|------|----------| | |
| 31 | +| `title_zh` | text | hanlp_index / hanlp_standard | 中文标题 | MySQL: `shoplazza_product_spu.title` | | |
| 32 | +| `title_en` | text | english | 英文标题 | 暂为空(待翻译服务填充) | | |
| 33 | + | |
| 34 | +#### 2.2 描述字段 | |
| 35 | + | |
| 36 | +| 字段名 | ES类型 | 分析器 | 说明 | 数据来源 | | |
| 37 | +|--------|--------|--------|------|----------| | |
| 38 | +| `brief_zh` | text | hanlp_index / hanlp_standard | 中文短描述 | MySQL: `shoplazza_product_spu.brief` | | |
| 39 | +| `brief_en` | text | english | 英文短描述 | 暂为空 | | |
| 40 | +| `description_zh` | text | hanlp_index / hanlp_standard | 中文详细描述 | MySQL: `shoplazza_product_spu.description` | | |
| 41 | +| `description_en` | text | english | 英文详细描述 | 暂为空 | | |
| 42 | + | |
| 43 | +#### 2.3 供应商/品牌字段 | |
| 44 | + | |
| 45 | +| 字段名 | ES类型 | 分析器 | 子字段 | 说明 | 数据来源 | | |
| 46 | +|--------|--------|--------|--------|------|----------| | |
| 47 | +| `vendor_zh` | text | hanlp_index / hanlp_standard | `vendor_zh.keyword` (keyword, normalizer: lowercase) | 中文供应商/品牌 | MySQL: `shoplazza_product_spu.vendor` | | |
| 48 | +| `vendor_en` | text | english | `vendor_en.keyword` (keyword, normalizer: lowercase) | 英文供应商/品牌 | 暂为空 | | |
| 49 | + | |
| 50 | +**用途**: | |
| 51 | +- `text` 类型:用于全文搜索(支持模糊匹配) | |
| 52 | +- `keyword` 子字段:用于精确匹配过滤和分面聚合 | |
| 53 | + | |
| 54 | +### 3. 标签字段 | |
| 55 | + | |
| 56 | +| 字段名 | ES类型 | 说明 | 数据来源 | | |
| 57 | +|--------|--------|------|----------| | |
| 58 | +| `tags` | keyword | 标签列表(数组) | MySQL: `shoplazza_product_spu.tags`(逗号分隔字符串,转换为数组) | | |
| 59 | + | |
| 60 | +**数据格式**: `["新品", "热卖", "爆款"]` | |
| 61 | + | |
| 62 | +### 4. 类目字段 | |
| 63 | + | |
| 64 | +#### 4.1 类目路径(用于搜索) | |
| 65 | + | |
| 66 | +| 字段名 | ES类型 | 分析器 | 说明 | 数据来源 | | |
| 67 | +|--------|--------|--------|------|----------| | |
| 68 | +| `category_path_zh` | text | hanlp_index / hanlp_standard | 中文类目路径(如"服装/男装/衬衫") | MySQL: `shoplazza_product_spu.category_path` | | |
| 69 | +| `category_path_en` | text | english | 英文类目路径 | 暂为空 | | |
| 70 | + | |
| 71 | +#### 4.2 类目名称(用于搜索) | |
| 72 | + | |
| 73 | +| 字段名 | ES类型 | 分析器 | 说明 | 数据来源 | | |
| 74 | +|--------|--------|--------|------|----------| | |
| 75 | +| `category_name_zh` | text | hanlp_index / hanlp_standard | 中文类目名称 | MySQL: `shoplazza_product_spu.category` | | |
| 76 | +| `category_name_en` | text | english | 英文类目名称 | 暂为空 | | |
| 77 | + | |
| 78 | +#### 4.3 类目标识(用于过滤和分面) | |
| 79 | + | |
| 80 | +| 字段名 | ES类型 | 说明 | 数据来源 | | |
| 81 | +|--------|--------|------|----------| | |
| 82 | +| `category_id` | keyword | 类目ID | MySQL: `shoplazza_product_spu.category_id` | | |
| 83 | +| `category_name` | keyword | 类目名称(用于过滤) | MySQL: `shoplazza_product_spu.category` | | |
| 84 | +| `category_level` | integer | 类目层级(1/2/3) | MySQL: `shoplazza_product_spu.category_level` | | |
| 85 | +| `category1_name` | keyword | 一级类目名称 | 从 `category_path` 解析 | | |
| 86 | +| `category2_name` | keyword | 二级类目名称 | 从 `category_path` 解析 | | |
| 87 | +| `category3_name` | keyword | 三级类目名称 | 从 `category_path` 解析 | | |
| 88 | + | |
| 89 | +**用途**: | |
| 90 | +- `category_path_zh/en`, `category_name_zh/en`: 用于全文搜索,支持模糊匹配 | |
| 91 | +- `category_id`, `category_name`, `category_level`, `category1/2/3_name`: 用于精确过滤和分面聚合 | |
| 92 | + | |
| 93 | +### 5. 规格字段(Specifications) | |
| 30 | 94 | |
| 31 | - | |
| 32 | - "min_price": min(v.price for v in variants), | |
| 33 | - "max_price": max(v.price for v in variants) | |
| 95 | +| 字段名 | ES类型 | 说明 | 数据来源 | | |
| 96 | +|--------|--------|------|----------| | |
| 97 | +| `specifications` | nested | 规格列表(嵌套对象数组) | MySQL: `shoplazza_product_option` + `shoplazza_product_sku.option1/2/3` | | |
| 98 | + | |
| 99 | +**嵌套结构**: | |
| 100 | +```json | |
| 101 | +{ | |
| 102 | + "specifications": [ | |
| 103 | + { | |
| 104 | + "sku_id": "sku_123", | |
| 105 | + "name": "color", | |
| 106 | + "value": "white" | |
| 107 | + }, | |
| 108 | + { | |
| 109 | + "sku_id": "sku_123", | |
| 110 | + "name": "size", | |
| 111 | + "value": "256GB" | |
| 34 | 112 | } |
| 35 | -1.2 查询方案 | |
| 36 | -对数组字段使用 dis_max,只取最高分,避免累加。 | |
| 37 | -其他重点字段 | |
| 38 | -1. Sku title | |
| 39 | -2. category | |
| 40 | -2.1 Mysql | |
| 41 | -在spu表中: | |
| 42 | -Field Type | |
| 43 | -category varchar(255) | |
| 44 | -category_id bigint(20) | |
| 45 | -category_google_id bigint(20) | |
| 46 | -category_level int(11) | |
| 47 | -category_path varchar(500) | |
| 48 | -2.2 ES索引 | |
| 49 | -2.2.1 输入数据 | |
| 50 | - 设计 1,2,3级分类 三个字段,的 category (原始文本) | |
| 51 | -2.2.2 索引方法 | |
| 52 | - 设计要求: | |
| 53 | - 1. 支持facet(精确过滤、keyword聚合),并且性能需要足够高。 | |
| 54 | - 2. 支持普通搜索模糊匹配(用户原始query可能包括分类词)。 | |
| 55 | - 3. 模糊匹配要考虑多语言 | |
| 56 | -方案:采用方案2 | |
| 57 | - 1. categoryPath索引 + Prefix 查询(categoryPath.keyword: "服装/男装")(如果满足条件的key太多的则性能较差,比如 查询的是一级类目,类目树叶子节点太多时性能较差) | |
| 58 | - 2. categoryPath支撑模糊查询 和 多级cate keyword索引支撑精确查询。 索引阶段冗余,查询性能高。 | |
| 59 | - "category_path_zh": { // 提供模糊查询功能,辅助相关性计算 | |
| 60 | - "type": "text", | |
| 61 | - "analyzer": "hanlp_index", | |
| 62 | - "search_analyzer": "hanlp_standard" | |
| 63 | - }, | |
| 64 | - "category_path_en": { // 提供模糊查询功能,辅助相关性计算 | |
| 65 | - "type": "text", | |
| 66 | - "analyzer": "english", | |
| 67 | - "search_analyzer": "english" | |
| 68 | - }, | |
| 69 | - "category_path": { // 用于多层级的筛选、精确匹配 | |
| 70 | - "type": "keyword", | |
| 71 | - "normalizer": "lowercase" | |
| 72 | - }, | |
| 73 | - "category_id": { | |
| 74 | - "type": "keyword" | |
| 75 | - }, | |
| 76 | - "category_name": { | |
| 77 | - "type": "keyword" | |
| 78 | - }, | |
| 79 | - "category_level": { | |
| 80 | - "type": "integer" | |
| 81 | - }, | |
| 82 | - "category1_name": { // 不同层级下 可能有同名的情况,因此提供一二三级分开的查询方式 | |
| 83 | - "type": "keyword" | |
| 84 | - }, | |
| 85 | - "category2_name": { | |
| 86 | - "type": "keyword" | |
| 87 | - }, | |
| 88 | - "category3_name": { | |
| 89 | - "type": "keyword" | |
| 90 | - }, | |
| 91 | - | |
| 92 | -3. tags | |
| 93 | -3.1 数据源 | |
| 94 | -多值 | |
| 95 | -标签 | |
| 96 | -最多输入250个标签,每个不得超过500字符,多个标签请用「英文逗号」隔开 | |
| 97 | -新品,热卖,爆款 | |
| 98 | -耳机,头戴式,爆款 | |
| 99 | - | |
| 100 | -分割后 list形式灌入 | |
| 101 | -3.2 Mysql | |
| 102 | -3.3 ES索引 | |
| 103 | -3.3.1 输入数据 | |
| 104 | -3.3.2 索引方法 | |
| 105 | -4. 供应商 | |
| 106 | -4.1 数据源 | |
| 107 | -4.2 Mysql | |
| 108 | -4.3 ES索引 | |
| 109 | -4.3.1 输入数据 | |
| 110 | -4.3.2 索引方法 | |
| 111 | -5. 款式/选项值(options) | |
| 112 | -5.1 数据源 | |
| 113 | -以下区域字段,商品属性为M(商品主体)的行需填写款式名称,商品属性为P(子款式)的行需填写款式值信息,商品属性为S(单一款式商品)的行无需填写 | |
| 114 | -款式1 款式2 款式3 | |
| 115 | -最多255字符 最多255字符 最多255字符 | |
| 116 | -SIZE COLOR | |
| 117 | -S red | |
| 118 | -... | |
| 119 | -5.2 Mysql | |
| 120 | -1. API 在 SPU 的维度直接返回3个属性定义,存储在 shoplazza_product_option 中: | |
| 121 | -1. API在 SKU的维度直接返回3个属性值,存储在 shoplazza_product_sku 表的 option 相关的字段中: | |
| 122 | -5.3 ES索引 | |
| 123 | - | |
| 124 | - "specifications": { | |
| 125 | - "type": "nested", | |
| 126 | - "properties": { | |
| 127 | - "name": { "type": "keyword" }, // "颜色", "容量" | |
| 128 | - "value": { "type": "keyword" } // "白色", "256GB" | |
| 113 | + ] | |
| 114 | +} | |
| 115 | +``` | |
| 116 | + | |
| 117 | +**数据来源**: | |
| 118 | +- `name`: 从 `shoplazza_product_option` 表获取(选项名称,如"color"、"size") | |
| 119 | +- `value`: 从 `shoplazza_product_sku` 表的 `option1`, `option2`, `option3` 字段获取(选项值,如"white"、"256GB") | |
| 120 | +- `sku_id`: SKU ID,用于关联 | |
| 121 | + | |
| 122 | +**API 过滤示例**: | |
| 123 | +```json | |
| 124 | +{ | |
| 125 | + "query": "手机", | |
| 126 | + "filters": { | |
| 127 | + "specifications": { | |
| 128 | + "name": "color", | |
| 129 | + "value": "white" | |
| 129 | 130 | } |
| 130 | - }, | |
| 131 | - | |
| 132 | - 另外还需要包含一个单独的字段,main_option (即店铺主题装修里面配置的 颜色切换 - 变体名称,也就是列表页商品的子sku显示维度) | |
| 133 | - "main_option": { "type": "keyword" } | |
| 134 | -查询指定款式 | |
| 131 | + } | |
| 132 | +} | |
| 133 | +``` | |
| 134 | + | |
| 135 | +**多个规格过滤(OR逻辑)**: | |
| 136 | +```json | |
| 135 | 137 | { |
| 136 | - "query": { | |
| 137 | - "nested": { | |
| 138 | - "path": "specifications", | |
| 139 | - "query": { | |
| 140 | - "bool": { | |
| 141 | - "must": [ | |
| 142 | - { "term": { "specifications.name ": "颜色" } }, | |
| 143 | - { "term": { "specifications.value": "绿色" } } | |
| 144 | - ] | |
| 145 | - } | |
| 138 | + "query": "手机", | |
| 139 | + "filters": { | |
| 140 | + "specifications": [ | |
| 141 | + {"name": "color", "value": "white"}, | |
| 142 | + {"name": "size", "value": "256GB"} | |
| 143 | + ] | |
| 144 | + } | |
| 145 | +} | |
| 146 | +``` | |
| 147 | + | |
| 148 | +**ES 查询结构**(后端自动生成): | |
| 149 | +```json | |
| 150 | +{ | |
| 151 | + "nested": { | |
| 152 | + "path": "specifications", | |
| 153 | + "query": { | |
| 154 | + "bool": { | |
| 155 | + "must": [ | |
| 156 | + { "term": { "specifications.name": "color" } }, | |
| 157 | + { "term": { "specifications.value": "white" } } | |
| 158 | + ] | |
| 146 | 159 | } |
| 147 | 160 | } |
| 148 | 161 | } |
| 149 | 162 | } |
| 150 | -按 name 做分面搜索(聚合) | |
| 151 | - | |
| 163 | +``` | |
| 164 | + | |
| 165 | +**API 分面示例**: | |
| 166 | + | |
| 167 | +所有规格名称的分面: | |
| 168 | +```json | |
| 169 | +{ | |
| 170 | + "query": "手机", | |
| 171 | + "facets": ["specifications"] | |
| 172 | +} | |
| 173 | +``` | |
| 174 | + | |
| 175 | +指定规格名称的分面: | |
| 176 | +```json | |
| 177 | +{ | |
| 178 | + "query": "手机", | |
| 179 | + "facets": ["specifications.color", "specifications.size"] | |
| 180 | +} | |
| 181 | +``` | |
| 182 | + | |
| 183 | +**ES 聚合结构**(后端自动生成): | |
| 184 | + | |
| 185 | +所有规格名称: | |
| 186 | +```json | |
| 152 | 187 | { |
| 153 | 188 | "aggs": { |
| 154 | - "specs": { | |
| 189 | + "specifications_facet": { | |
| 155 | 190 | "nested": { "path": "specifications" }, |
| 156 | 191 | "aggs": { |
| 157 | 192 | "by_name": { |
| 158 | - "terms": { | |
| 159 | - "field": "specifications.name", | |
| 160 | - "size": 20 | |
| 161 | - }, | |
| 193 | + "terms": { "field": "specifications.name", "size": 20 }, | |
| 162 | 194 | "aggs": { |
| 163 | 195 | "value_counts": { |
| 164 | - "terms": { | |
| 165 | - "field": "specifications.value", | |
| 166 | - "size": 10 | |
| 167 | - } | |
| 196 | + "terms": { "field": "specifications.value", "size": 10 } | |
| 168 | 197 | } |
| 169 | 198 | } |
| 170 | 199 | } |
| ... | ... | @@ -172,4 +201,204 @@ S red |
| 172 | 201 | } |
| 173 | 202 | } |
| 174 | 203 | } |
| 175 | - | |
| 176 | 204 | \ No newline at end of file |
| 205 | +``` | |
| 206 | + | |
| 207 | +指定规格名称: | |
| 208 | +```json | |
| 209 | +{ | |
| 210 | + "aggs": { | |
| 211 | + "specifications_color_facet": { | |
| 212 | + "nested": { "path": "specifications" }, | |
| 213 | + "aggs": { | |
| 214 | + "filter_by_name": { | |
| 215 | + "filter": { "term": { "specifications.name": "color" } }, | |
| 216 | + "aggs": { | |
| 217 | + "value_counts": { | |
| 218 | + "terms": { "field": "specifications.value", "size": 10 } | |
| 219 | + } | |
| 220 | + } | |
| 221 | + } | |
| 222 | + } | |
| 223 | + } | |
| 224 | + } | |
| 225 | +} | |
| 226 | +``` | |
| 227 | + | |
| 228 | +### 6. 选项名称字段 | |
| 229 | + | |
| 230 | +| 字段名 | ES类型 | 说明 | 数据来源 | | |
| 231 | +|--------|--------|------|----------| | |
| 232 | +| `option1_name` | keyword | 选项1名称(如"color") | MySQL: `shoplazza_product_option` | | |
| 233 | +| `option2_name` | keyword | 选项2名称(如"size") | MySQL: `shoplazza_product_option` | | |
| 234 | +| `option3_name` | keyword | 选项3名称 | MySQL: `shoplazza_product_option` | | |
| 235 | + | |
| 236 | +### 7. 价格字段 | |
| 237 | + | |
| 238 | +| 字段名 | ES类型 | 说明 | 数据来源 | | |
| 239 | +|--------|--------|------|----------| | |
| 240 | +| `min_price` | float | 最低价格 | 从所有 SKU 价格计算 | | |
| 241 | +| `max_price` | float | 最高价格 | 从所有 SKU 价格计算 | | |
| 242 | +| `compare_at_price` | float | 原价/对比价 | MySQL: `shoplazza_product_spu.compare_at_price` | | |
| 243 | +| `sku_prices` | float | 所有 SKU 价格列表(数组) | 从所有 SKU 价格汇总 | | |
| 244 | + | |
| 245 | +### 8. 重量字段 | |
| 246 | + | |
| 247 | +| 字段名 | ES类型 | 说明 | 数据来源 | | |
| 248 | +|--------|--------|------|----------| | |
| 249 | +| `sku_weights` | long | 所有 SKU 重量列表(数组) | 从所有 SKU 重量汇总 | | |
| 250 | +| `sku_weight_units` | keyword | 所有 SKU 重量单位列表(数组) | 从所有 SKU 重量单位汇总 | | |
| 251 | + | |
| 252 | +### 9. 库存字段 | |
| 253 | + | |
| 254 | +| 字段名 | ES类型 | 说明 | 数据来源 | | |
| 255 | +|--------|--------|------|----------| | |
| 256 | +| `total_inventory` | long | 总库存(所有 SKU 库存之和) | 从所有 SKU 库存汇总 | | |
| 257 | + | |
| 258 | +### 10. SKU 嵌套字段 | |
| 259 | + | |
| 260 | +| 字段名 | ES类型 | 说明 | 数据来源 | | |
| 261 | +|--------|--------|------|----------| | |
| 262 | +| `skus` | nested | SKU 详细信息列表(嵌套对象数组) | MySQL: `shoplazza_product_sku` | | |
| 263 | + | |
| 264 | +**嵌套结构**: | |
| 265 | +```json | |
| 266 | +{ | |
| 267 | + "skus": [ | |
| 268 | + { | |
| 269 | + "sku_id": "sku_123", | |
| 270 | + "price": 99.99, | |
| 271 | + "compare_at_price": 149.99, | |
| 272 | + "sku_code": "SKU001", | |
| 273 | + "stock": 100, | |
| 274 | + "weight": 0.5, | |
| 275 | + "weight_unit": "kg", | |
| 276 | + "option1_value": "white", | |
| 277 | + "option2_value": "256GB", | |
| 278 | + "option3_value": null, | |
| 279 | + "image_src": "https://example.com/image.jpg" | |
| 280 | + } | |
| 281 | + ] | |
| 282 | +} | |
| 283 | +``` | |
| 284 | + | |
| 285 | +**字段说明**: | |
| 286 | +- `sku_id`: SKU 唯一标识 | |
| 287 | +- `price`: SKU 价格 | |
| 288 | +- `compare_at_price`: SKU 原价 | |
| 289 | +- `sku_code`: SKU 编码 | |
| 290 | +- `stock`: 库存数量 | |
| 291 | +- `weight`: 重量 | |
| 292 | +- `weight_unit`: 重量单位 | |
| 293 | +- `option1_value`, `option2_value`, `option3_value`: 选项值(对应 `option1_name`, `option2_name`, `option3_name`) | |
| 294 | +- `image_src`: SKU 图片地址(`index: false`,仅用于返回) | |
| 295 | + | |
| 296 | +### 11. 图片字段 | |
| 297 | + | |
| 298 | +| 字段名 | ES类型 | 说明 | 数据来源 | | |
| 299 | +|--------|--------|------|----------| | |
| 300 | +| `image_url` | keyword | 主图URL(`index: false`,仅用于返回) | MySQL: `shoplazza_product_spu.image_url` | | |
| 301 | + | |
| 302 | +### 12. 向量字段(不返回给前端) | |
| 303 | + | |
| 304 | +| 字段名 | ES类型 | 维度 | 说明 | 数据来源 | | |
| 305 | +|--------|--------|------|------|----------| | |
| 306 | +| `title_embedding` | dense_vector | 1024 | 标题向量(用于语义搜索) | 由 BGE-M3 模型生成 | | |
| 307 | +| `image_embedding` | nested | - | 图片向量(用于图片搜索) | 由 CN-CLIP 模型生成 | | |
| 308 | + | |
| 309 | +**注意**: 这些字段仅用于搜索,不会返回给前端。 | |
| 310 | + | |
| 311 | +## 字段用途总结 | |
| 312 | + | |
| 313 | +### 搜索字段(参与相关性计算) | |
| 314 | + | |
| 315 | +- `title_zh`, `title_en` (boost: 3.0) | |
| 316 | +- `brief_zh`, `brief_en` (boost: 1.5) | |
| 317 | +- `description_zh`, `description_en` (boost: 1.0) | |
| 318 | +- `vendor_zh`, `vendor_en` (boost: 1.5) | |
| 319 | +- `tags` (boost: 1.0) | |
| 320 | +- `category_path_zh`, `category_path_en` (boost: 1.5) | |
| 321 | +- `category_name_zh`, `category_name_en` (boost: 1.5) | |
| 322 | +- `title_embedding` (向量召回,boost: 0.2) | |
| 323 | + | |
| 324 | +### 过滤字段(精确匹配) | |
| 325 | + | |
| 326 | +- `tenant_id` (必需,多租户隔离) | |
| 327 | +- `category_id`, `category_name`, `category1_name`, `category2_name`, `category3_name` | |
| 328 | +- `vendor_zh.keyword`, `vendor_en.keyword` | |
| 329 | +- `specifications` (嵌套查询) | |
| 330 | +- `min_price`, `max_price` (范围过滤) | |
| 331 | + | |
| 332 | +### 分面字段(聚合统计) | |
| 333 | + | |
| 334 | +- `category1_name`, `category2_name`, `category3_name` | |
| 335 | +- `specifications` (所有规格名称的分面,嵌套聚合,按 name 分组,然后按 value 聚合) | |
| 336 | +- `specifications.{name}` (指定规格名称的分面,如 `specifications.color`,只返回该 name 的 value 列表) | |
| 337 | + | |
| 338 | +### 返回字段(前端展示) | |
| 339 | + | |
| 340 | +除 `title_embedding` 和 `image_embedding` 外,所有字段都会根据 `language` 参数自动选择对应的中英文字段返回。 | |
| 341 | + | |
| 342 | +## 数据映射规则 | |
| 343 | + | |
| 344 | +### 多语言字段映射 | |
| 345 | + | |
| 346 | +后端根据请求的 `language` 参数(`zh` 或 `en`)自动选择: | |
| 347 | + | |
| 348 | +- `language="zh"`: 优先返回 `*_zh` 字段,如果为空则回退到 `*_en` 字段 | |
| 349 | +- `language="en"`: 优先返回 `*_en` 字段,如果为空则回退到 `*_zh` 字段 | |
| 350 | + | |
| 351 | +映射到前端字段: | |
| 352 | +- `title_zh/en` → `title` | |
| 353 | +- `brief_zh/en` → `brief` | |
| 354 | +- `description_zh/en` → `description` | |
| 355 | +- `vendor_zh/en` → `vendor` | |
| 356 | +- `category_path_zh/en` → `category_path` | |
| 357 | +- `category_name_zh/en` → `category_name` | |
| 358 | + | |
| 359 | +### 规格数据构建 | |
| 360 | + | |
| 361 | +1. 从 `shoplazza_product_option` 表获取选项名称(`option1_name`, `option2_name`, `option3_name`) | |
| 362 | +2. 从 `shoplazza_product_sku` 表获取选项值(`option1`, `option2`, `option3`) | |
| 363 | +3. 将每个 SKU 的选项组合构建为 `specifications` 数组: | |
| 364 | + ```python | |
| 365 | + for sku in skus: | |
| 366 | + if sku.option1 and option1_name: | |
| 367 | + specifications.append({ | |
| 368 | + "sku_id": sku.id, | |
| 369 | + "name": option1_name, # 如"color" | |
| 370 | + "value": sku.option1 # 如"white" | |
| 371 | + }) | |
| 372 | + # 同样处理 option2, option3 | |
| 373 | + ``` | |
| 374 | + | |
| 375 | +## 查询架构 | |
| 376 | + | |
| 377 | +### 查询结构 | |
| 378 | + | |
| 379 | +``` | |
| 380 | +filters AND (text_recall OR embedding_recall) | |
| 381 | +``` | |
| 382 | + | |
| 383 | +- **filters**: 前端传递的过滤条件(永远起作用) | |
| 384 | +- **text_recall**: 文本相关性召回(同时搜索中英文字段) | |
| 385 | +- **embedding_recall**: 向量召回(KNN) | |
| 386 | +- **function_score**: 包装召回部分,支持提权字段(新鲜度、销量等) | |
| 387 | + | |
| 388 | +### 文本召回字段 | |
| 389 | + | |
| 390 | +默认同时搜索以下字段(中英文都包含): | |
| 391 | +- `title_zh^3.0`, `title_en^3.0` | |
| 392 | +- `brief_zh^1.5`, `brief_en^1.5` | |
| 393 | +- `description_zh^1.0`, `description_en^1.0` | |
| 394 | +- `vendor_zh^1.5`, `vendor_en^1.5` | |
| 395 | +- `category_path_zh^1.5`, `category_path_en^1.5` | |
| 396 | +- `category_name_zh^1.5`, `category_name_en^1.5` | |
| 397 | +- `tags^1.0` | |
| 398 | + | |
| 399 | +## 注意事项 | |
| 400 | + | |
| 401 | +1. **索引维度**: 所有数据以 SPU 为单位索引,SKU 信息作为嵌套字段存储 | |
| 402 | +2. **多租户隔离**: 所有查询必须包含 `tenant_id` 过滤条件 | |
| 403 | +3. **多语言支持**: 文本字段支持中英文,后端根据 `language` 参数自动选择 | |
| 404 | +4. **规格分面**: `specifications` 使用嵌套聚合,按 `name` 分组,然后按 `value` 聚合 | |
| 405 | +5. **向量字段**: `title_embedding` 和 `image_embedding` 仅用于搜索,不返回给前端 | ... | ... |
search/es_query_builder.py
| ... | ... | @@ -352,6 +352,57 @@ class ESQueryBuilder: |
| 352 | 352 | # 1. 处理精确匹配过滤 |
| 353 | 353 | if filters: |
| 354 | 354 | for field, value in filters.items(): |
| 355 | + # 特殊处理:specifications 嵌套过滤 | |
| 356 | + if field == "specifications": | |
| 357 | + if isinstance(value, dict): | |
| 358 | + # 单个规格过滤:{"name": "color", "value": "green"} | |
| 359 | + name = value.get("name") | |
| 360 | + spec_value = value.get("value") | |
| 361 | + if name and spec_value: | |
| 362 | + filter_clauses.append({ | |
| 363 | + "nested": { | |
| 364 | + "path": "specifications", | |
| 365 | + "query": { | |
| 366 | + "bool": { | |
| 367 | + "must": [ | |
| 368 | + {"term": {"specifications.name": name}}, | |
| 369 | + {"term": {"specifications.value": spec_value}} | |
| 370 | + ] | |
| 371 | + } | |
| 372 | + } | |
| 373 | + } | |
| 374 | + }) | |
| 375 | + elif isinstance(value, list): | |
| 376 | + # 多个规格过滤(OR逻辑):[{"name": "color", "value": "green"}, ...] | |
| 377 | + should_clauses = [] | |
| 378 | + for spec in value: | |
| 379 | + if isinstance(spec, dict): | |
| 380 | + name = spec.get("name") | |
| 381 | + spec_value = spec.get("value") | |
| 382 | + if name and spec_value: | |
| 383 | + should_clauses.append({ | |
| 384 | + "nested": { | |
| 385 | + "path": "specifications", | |
| 386 | + "query": { | |
| 387 | + "bool": { | |
| 388 | + "must": [ | |
| 389 | + {"term": {"specifications.name": name}}, | |
| 390 | + {"term": {"specifications.value": spec_value}} | |
| 391 | + ] | |
| 392 | + } | |
| 393 | + } | |
| 394 | + } | |
| 395 | + }) | |
| 396 | + if should_clauses: | |
| 397 | + filter_clauses.append({ | |
| 398 | + "bool": { | |
| 399 | + "should": should_clauses, | |
| 400 | + "minimum_should_match": 1 | |
| 401 | + } | |
| 402 | + }) | |
| 403 | + continue | |
| 404 | + | |
| 405 | + # 普通字段过滤 | |
| 355 | 406 | if isinstance(value, list): |
| 356 | 407 | # 多值匹配(OR) |
| 357 | 408 | filter_clauses.append({ |
| ... | ... | @@ -486,34 +537,62 @@ class ESQueryBuilder: |
| 486 | 537 | |
| 487 | 538 | for config in facet_configs: |
| 488 | 539 | # 特殊处理:specifications嵌套分面 |
| 489 | - if isinstance(config, str) and config == "specifications": | |
| 490 | - # 构建specifications嵌套分面(按name聚合,然后按value聚合) | |
| 491 | - aggs["specifications_facet"] = { | |
| 492 | - "nested": { | |
| 493 | - "path": "specifications" | |
| 494 | - }, | |
| 495 | - "aggs": { | |
| 496 | - "by_name": { | |
| 497 | - "terms": { | |
| 498 | - "field": "specifications.name", | |
| 499 | - "size": 20, | |
| 500 | - "order": {"_count": "desc"} | |
| 501 | - }, | |
| 502 | - "aggs": { | |
| 503 | - "value_counts": { | |
| 504 | - "terms": { | |
| 505 | - "field": "specifications.value", | |
| 506 | - "size": 10, | |
| 507 | - "order": {"_count": "desc"} | |
| 540 | + if isinstance(config, str): | |
| 541 | + # 格式1: "specifications" - 返回所有name的分面 | |
| 542 | + if config == "specifications": | |
| 543 | + aggs["specifications_facet"] = { | |
| 544 | + "nested": { | |
| 545 | + "path": "specifications" | |
| 546 | + }, | |
| 547 | + "aggs": { | |
| 548 | + "by_name": { | |
| 549 | + "terms": { | |
| 550 | + "field": "specifications.name", | |
| 551 | + "size": 20, | |
| 552 | + "order": {"_count": "desc"} | |
| 553 | + }, | |
| 554 | + "aggs": { | |
| 555 | + "value_counts": { | |
| 556 | + "terms": { | |
| 557 | + "field": "specifications.value", | |
| 558 | + "size": 10, | |
| 559 | + "order": {"_count": "desc"} | |
| 560 | + } | |
| 508 | 561 | } |
| 509 | 562 | } |
| 510 | 563 | } |
| 511 | 564 | } |
| 512 | 565 | } |
| 513 | - } | |
| 514 | - continue | |
| 566 | + continue | |
| 567 | + | |
| 568 | + # 格式2: "specifications.color" 或 "specifications.颜色" - 只返回指定name的value列表 | |
| 569 | + if config.startswith("specifications."): | |
| 570 | + name = config[len("specifications."):] | |
| 571 | + agg_name = f"specifications_{name}_facet" | |
| 572 | + aggs[agg_name] = { | |
| 573 | + "nested": { | |
| 574 | + "path": "specifications" | |
| 575 | + }, | |
| 576 | + "aggs": { | |
| 577 | + "filter_by_name": { | |
| 578 | + "filter": { | |
| 579 | + "term": {"specifications.name": name} | |
| 580 | + }, | |
| 581 | + "aggs": { | |
| 582 | + "value_counts": { | |
| 583 | + "terms": { | |
| 584 | + "field": "specifications.value", | |
| 585 | + "size": 10, | |
| 586 | + "order": {"_count": "desc"} | |
| 587 | + } | |
| 588 | + } | |
| 589 | + } | |
| 590 | + } | |
| 591 | + } | |
| 592 | + } | |
| 593 | + continue | |
| 515 | 594 | |
| 516 | - # 简单模式:只有字段名(字符串) | |
| 595 | + # 简单模式:只有字段名(字符串,非specifications) | |
| 517 | 596 | if isinstance(config, str): |
| 518 | 597 | field = config |
| 519 | 598 | agg_name = f"{field}_facet" |
| ... | ... | @@ -524,6 +603,7 @@ class ESQueryBuilder: |
| 524 | 603 | "order": {"_count": "desc"} |
| 525 | 604 | } |
| 526 | 605 | } |
| 606 | + continue | |
| 527 | 607 | |
| 528 | 608 | # 高级模式:FacetConfig 对象 |
| 529 | 609 | else: | ... | ... |