Commit 6d524cb4993a3530ae4e99378afb20e77a6bb528

Authored by tangwang
1 parent c581becd

docs优化

docs/multi_select_faceting.md deleted
@@ -1,399 +0,0 @@ @@ -1,399 +0,0 @@
1 -# Multi-Select Faceting 功能说明  
2 -  
3 -## 概述  
4 -  
5 -Multi-Select Faceting(多选分面)是业界标准的 Faceted Search 功能,允许用户在选中某个分面筛选项后,仍然能看到该分面的其他可选项,提供更好的探索式搜索体验。  
6 -  
7 -## 功能特性  
8 -  
9 -### 1. 两种 Faceting 模式  
10 -  
11 -#### 标准模式(Conjunctive Faceting)  
12 -- **设置**: `multi_select: false`(默认)  
13 -- **行为**: 选中某个分面值后,该分面只显示选中的值  
14 -- **适用场景**: 层级下钻、逐步精炼  
15 -- **ES 实现**: 过滤器应用在 `query.bool.filter`  
16 -  
17 -#### Multi-Select 模式(Disjunctive Faceting)  
18 -- **设置**: `multi_select: true`  
19 -- **行为**: 选中某个分面值后,该分面仍显示所有可选项  
20 -- **适用场景**: 颜色、品牌、尺码等可切换属性  
21 -- **ES 实现**: 过滤器应用在 `post_filter`  
22 -  
23 -### 2. Selected 状态标记  
24 -  
25 -所有 facet 值都包含 `selected` 字段,标记当前是否被选中:  
26 -- `selected: true` - 当前筛选项已被选中  
27 -- `selected: false` - 当前筛选项未被选中  
28 -  
29 -## 使用示例  
30 -  
31 -### 示例 1: 标准 Category Faceting  
32 -  
33 -```json  
34 -{  
35 - "query": "T恤",  
36 - "filters": {  
37 - "category1_name": "服装"  
38 - },  
39 - "facets": [  
40 - {  
41 - "field": "category1_name",  
42 - "size": 10,  
43 - "type": "terms",  
44 - "multi_select": false  
45 - }  
46 - ]  
47 -}  
48 -```  
49 -  
50 -**响应**:  
51 -```json  
52 -{  
53 - "results": [...],  
54 - "facets": [  
55 - {  
56 - "field": "category1_name",  
57 - "values": [  
58 - {"value": "服装", "count": 150, "selected": true}  
59 - ]  
60 - }  
61 - ]  
62 -}  
63 -```  
64 -  
65 -### 示例 2: Multi-Select Brand Faceting  
66 -  
67 -```json  
68 -{  
69 - "query": "手机",  
70 - "filters": {  
71 - "brand_name": "苹果"  
72 - },  
73 - "facets": [  
74 - {  
75 - "field": "brand_name",  
76 - "size": 10,  
77 - "type": "terms",  
78 - "multi_select": true  
79 - }  
80 - ]  
81 -}  
82 -```  
83 -  
84 -**响应**:  
85 -```json  
86 -{  
87 - "results": [...只包含苹果手机...],  
88 - "facets": [  
89 - {  
90 - "field": "brand_name",  
91 - "values": [  
92 - {"value": "苹果", "count": 150, "selected": true},  
93 - {"value": "华为", "count": 120, "selected": false},  
94 - {"value": "小米", "count": 98, "selected": false}  
95 - ]  
96 - }  
97 - ]  
98 -}  
99 -```  
100 -  
101 -### 示例 3: Specifications Multi-Select  
102 -  
103 -```json  
104 -{  
105 - "query": "衬衫",  
106 - "filters": {  
107 - "specifications": {  
108 - "name": "颜色",  
109 - "value": "白色"  
110 - }  
111 - },  
112 - "facets": [  
113 - {  
114 - "field": "specifications.颜色",  
115 - "size": 10,  
116 - "type": "terms",  
117 - "multi_select": true  
118 - },  
119 - {  
120 - "field": "specifications.尺码",  
121 - "size": 10,  
122 - "type": "terms",  
123 - "multi_select": false  
124 - }  
125 - ]  
126 -}  
127 -```  
128 -  
129 -**响应**:  
130 -```json  
131 -{  
132 - "results": [...只包含白色衬衫...],  
133 - "facets": [  
134 - {  
135 - "field": "specifications.颜色",  
136 - "label": "颜色",  
137 - "values": [  
138 - {"value": "白色", "count": 50, "selected": true},  
139 - {"value": "蓝色", "count": 35, "selected": false},  
140 - {"value": "黑色", "count": 28, "selected": false}  
141 - ]  
142 - },  
143 - {  
144 - "field": "specifications.尺码",  
145 - "label": "尺码",  
146 - "values": [  
147 - {"value": "M", "count": 20, "selected": false},  
148 - {"value": "L", "count": 18, "selected": false},  
149 - {"value": "XL", "count": 12, "selected": false}  
150 - ]  
151 - }  
152 - ]  
153 -}  
154 -```  
155 -  
156 -注意:尺码分面(`multi_select: false`)的统计是基于白色衬衫的。  
157 -  
158 -### 示例 4: 混合多个 Multi-Select Facets  
159 -  
160 -```json  
161 -{  
162 - "query": "*",  
163 - "filters": {  
164 - "category1_name": "玩具",  
165 - "specifications": [  
166 - {"name": "颜色", "value": "红色"},  
167 - {"name": "材质", "value": "塑料"}  
168 - ]  
169 - },  
170 - "facets": [  
171 - {  
172 - "field": "category1_name",  
173 - "size": 10,  
174 - "multi_select": true  
175 - },  
176 - {  
177 - "field": "specifications.颜色",  
178 - "size": 10,  
179 - "multi_select": true  
180 - },  
181 - {  
182 - "field": "specifications.材质",  
183 - "size": 10,  
184 - "multi_select": true  
185 - },  
186 - {  
187 - "field": "specifications.年龄段",  
188 - "size": 10,  
189 - "multi_select": false  
190 - }  
191 - ]  
192 -}  
193 -```  
194 -  
195 -**行为说明**:  
196 -- `category1_name`: 显示所有类目选项(玩具被标记为 selected)  
197 -- `specifications.颜色`: 显示所有颜色选项(红色被标记为 selected)  
198 -- `specifications.材质`: 显示所有材质选项(塑料被标记为 selected)  
199 -- `specifications.年龄段`: 只显示符合当前过滤条件的年龄段选项  
200 -  
201 -## 前端集成建议  
202 -  
203 -### React 示例  
204 -  
205 -```jsx  
206 -function FacetComponent({ facet }) {  
207 - return (  
208 - <div className="facet">  
209 - <h3>{facet.label}</h3>  
210 - {facet.values.map(value => (  
211 - <label key={value.value} className={value.selected ? 'active' : ''}>  
212 - <input  
213 - type="checkbox"  
214 - checked={value.selected}  
215 - onChange={() => toggleFilter(facet.field, value.value)}  
216 - />  
217 - {value.value} ({value.count})  
218 - </label>  
219 - ))}  
220 - </div>  
221 - );  
222 -}  
223 -```  
224 -  
225 -### Vue 示例  
226 -  
227 -```vue  
228 -<template>  
229 - <div class="facet">  
230 - <h3>{{ facet.label }}</h3>  
231 - <label  
232 - v-for="value in facet.values"  
233 - :key="value.value"  
234 - :class="{ active: value.selected }"  
235 - >  
236 - <input  
237 - type="checkbox"  
238 - :checked="value.selected"  
239 - @change="toggleFilter(facet.field, value.value)"  
240 - />  
241 - {{ value.value }} ({{ value.count }})  
242 - </label>  
243 - </div>  
244 -</template>  
245 -```  
246 -  
247 -## 技术实现细节  
248 -  
249 -### Elasticsearch Query 结构  
250 -  
251 -**Multi-Select Faceting** 使用 `post_filter` 实现:  
252 -  
253 -```json  
254 -{  
255 - "query": {  
256 - "bool": {  
257 - "must": [...],  
258 - "filter": [  
259 - // 只包含 multi_select=false 的过滤器  
260 - {"term": {"category2_name": "短袖T恤"}}  
261 - ]  
262 - }  
263 - },  
264 - "post_filter": {  
265 - "bool": {  
266 - "filter": [  
267 - // 包含 multi_select=true 的过滤器  
268 - {"term": {"brand_name": "苹果"}},  
269 - {  
270 - "nested": {  
271 - "path": "specifications",  
272 - "query": {  
273 - "bool": {  
274 - "must": [  
275 - {"term": {"specifications.name": "颜色"}},  
276 - {"term": {"specifications.value": "白色"}}  
277 - ]  
278 - }  
279 - }  
280 - }  
281 - }  
282 - ]  
283 - }  
284 - },  
285 - "aggs": {  
286 - // 所有聚合都基于 query 的结果(不受 post_filter 影响)  
287 - "brand_name_facet": {...},  
288 - "specifications_颜色_facet": {...}  
289 - }  
290 -}  
291 -```  
292 -  
293 -**关键点**:  
294 -- `query.bool.filter`: 影响结果和聚合  
295 -- `post_filter`: 只影响结果,不影响聚合  
296 -- 聚合统计基于 `query` 的结果,因此 multi-select facet 可以显示多个选项  
297 -  
298 -## 最佳实践  
299 -  
300 -### 1. 何时使用 Multi-Select  
301 -  
302 -| Facet 类型 | 推荐模式 | 原因 |  
303 -|-----------|---------|------|  
304 -| 颜色 | `multi_select: true` | 用户需要切换颜色 |  
305 -| 品牌 | `multi_select: true` | 用户需要比较不同品牌 |  
306 -| 尺码 | `multi_select: true` | 用户需要查看其他尺码 |  
307 -| 类目 | `multi_select: false` | 层级下钻 |  
308 -| 价格区间 | `multi_select: false` | 互斥选择 |  
309 -| 是否有货 | `multi_select: false` | 布尔值筛选 |  
310 -  
311 -### 2. 性能考虑  
312 -  
313 -- **过多 Multi-Select**: 会增加 ES 查询复杂度  
314 -- **建议**: 最多 3-5 个 multi-select facets  
315 -- **优化**: 对于不常用的属性使用标准模式  
316 -  
317 -### 3. UI 设计建议  
318 -  
319 -- **Multi-Select Facets**: 使用复选框(Checkbox)  
320 -- **Standard Facets**: 使用单选框(Radio)或链接  
321 -- **Selected 状态**: 使用不同颜色或图标标识  
322 -  
323 -## API 变更说明  
324 -  
325 -### 新增字段  
326 -  
327 -**FacetConfig**:  
328 -```json  
329 -{  
330 - "field": "brand_name",  
331 - "size": 10,  
332 - "type": "terms",  
333 - "multi_select": true // 新增字段  
334 -}  
335 -```  
336 -  
337 -**FacetValue**:  
338 -```json  
339 -{  
340 - "value": "苹果",  
341 - "count": 150,  
342 - "selected": true // 现在由后端返回真实状态  
343 -}  
344 -```  
345 -  
346 -### 兼容性  
347 -  
348 -- `multi_select` 默认为 `false`,保持向后兼容  
349 -- 旧版 API 调用仍然有效(使用标准模式)  
350 -  
351 -## 测试验证  
352 -  
353 -运行测试脚本:  
354 -```bash  
355 -python test_multi_select_facet.py  
356 -```  
357 -  
358 -测试覆盖:  
359 -1. ✓ 标准 Faceting (multi_select=false)  
360 -2. ✓ Multi-Select Faceting (multi_select=true)  
361 -3. ✓ Specifications Multi-Select  
362 -4. ✓ ES Query 结构验证  
363 -  
364 -## 故障排查  
365 -  
366 -### 问题 1: Multi-Select 不生效  
367 -  
368 -**症状**: 设置了 `multi_select: true`,但仍然只返回一个值  
369 -  
370 -**检查**:  
371 -1. 确认 `multi_select` 字段在请求中正确设置  
372 -2. 检查 ES query 是否包含 `post_filter`(开启 `debug: true`)  
373 -3. 验证 Elasticsearch 版本支持 `post_filter`  
374 -  
375 -### 问题 2: Selected 标记不正确  
376 -  
377 -**症状**: `selected` 字段没有正确标记  
378 -  
379 -**检查**:  
380 -1. 确认 `filters` 中的字段名与 facet 字段名一致  
381 -2. 对于 specifications,检查 `name` 和 `value` 是否匹配  
382 -3. 检查 `filters` 的值类型(字符串、数组等)  
383 -  
384 -### 问题 3: 性能问题  
385 -  
386 -**症状**: 启用 Multi-Select 后查询变慢  
387 -  
388 -**优化**:  
389 -1. 减少 multi-select facets 数量  
390 -2. 降低 facet `size` 参数  
391 -3. 考虑使用缓存  
392 -4. 为常用字段建立索引  
393 -  
394 -## 参考资料  
395 -  
396 -- [Elasticsearch Post Filter](https://www.elastic.co/guide/en/elasticsearch/reference/current/filter-search-results.html#post-filter)  
397 -- [Algolia Disjunctive Faceting](https://www.algolia.com/doc/guides/managing-results/refine-results/faceting/#conjunctive-and-disjunctive-facets)  
398 -- [Amazon Product Search](https://www.amazon.com) - 业界最佳实践示例  
399 -  
docs/搜索API对接指南.md
@@ -318,38 +318,17 @@ curl -X POST &quot;http://120.76.41.98:6002/search/&quot; \ @@ -318,38 +318,17 @@ curl -X POST &quot;http://120.76.41.98:6002/search/&quot; \
318 318
319 **重要特性**: `multi_select` 字段控制分面的行为模式。 319 **重要特性**: `multi_select` 字段控制分面的行为模式。
320 320
  321 +
321 ##### 标准模式 (multi_select: false) 322 ##### 标准模式 (multi_select: false)
322 - **行为**: 选中某个分面值后,该分面只显示选中的值 323 - **行为**: 选中某个分面值后,该分面只显示选中的值
323 - **适用场景**: 层级类目、互斥选择 324 - **适用场景**: 层级类目、互斥选择
324 - **示例**: 类目下钻(玩具 > 娃娃 > 芭比) 325 - **示例**: 类目下钻(玩具 > 娃娃 > 芭比)
325 326
326 -```json  
327 -{  
328 - "filters": {"category1_name": "玩具"},  
329 - "facets": [  
330 - {"field": "category1_name", "size": 10, "multi_select": false}  
331 - ]  
332 -}  
333 -```  
334 -**响应**: 只返回 "玩具" 一个选项  
335 -  
336 ##### Multi-Select 模式 (multi_select: true) ⭐ 327 ##### Multi-Select 模式 (multi_select: true) ⭐
337 - **行为**: 选中某个分面值后,该分面仍显示所有可选项 328 - **行为**: 选中某个分面值后,该分面仍显示所有可选项
338 - **适用场景**: 颜色、品牌、尺码等可切换属性 329 - **适用场景**: 颜色、品牌、尺码等可切换属性
339 - **示例**: 选择了"红色"后,仍能看到"蓝色"、"绿色"等选项 330 - **示例**: 选择了"红色"后,仍能看到"蓝色"、"绿色"等选项
340 331
341 -```json  
342 -{  
343 - "filters": {  
344 - "specifications": {"name": "颜色", "value": "红色"}  
345 - },  
346 - "facets": [  
347 - {"field": "specifications.颜色", "size": 10, "multi_select": true}  
348 - ]  
349 -}  
350 -```  
351 -**响应**: 返回所有颜色选项,"红色" 被标记为 `selected: true`  
352 -  
353 ##### 推荐配置 332 ##### 推荐配置
354 333
355 | 分面类型 | multi_select | 原因 | 334 | 分面类型 | multi_select | 原因 |
frontend/index.html
@@ -23,7 +23,7 @@ @@ -23,7 +23,7 @@
23 <div class="search-bar"> 23 <div class="search-bar">
24 <div class="tenant-input-wrapper"> 24 <div class="tenant-input-wrapper">
25 <label for="tenantInput">tenant ID:</label> 25 <label for="tenantInput">tenant ID:</label>
26 - <input type="text" id="tenantInput" placeholder="请输入租户ID" value="1"> 26 + <input type="text" id="tenantInput" placeholder="请输入租户ID" value="162">
27 </div> 27 </div>
28 <div class="tenant-input-wrapper"> 28 <div class="tenant-input-wrapper">
29 <label for="skuFilterDimension">sku_filter_dimension:</label> 29 <label for="skuFilterDimension">sku_filter_dimension:</label>
test_multi_select_facet.py deleted
@@ -1,288 +0,0 @@ @@ -1,288 +0,0 @@
1 -#!/usr/bin/env python3  
2 -"""  
3 -测试 Multi-Select Faceting 和 Selected 标记功能  
4 -  
5 -验证:  
6 -1. multi_select=False 时,选中某个 facet 值后,该 facet 只返回一个值(标准模式)  
7 -2. multi_select=True 时,选中某个 facet 值后,该 facet 仍返回多个值(disjunctive 模式)  
8 -3. selected 字段正确标记  
9 -"""  
10 -  
11 -import requests  
12 -import json  
13 -  
14 -API_URL = "http://localhost:8000/api/search"  
15 -TENANT_ID = "test_tenant"  
16 -  
17 -def test_standard_faceting():  
18 - """测试标准 faceting(multi_select=False)"""  
19 - print("\n" + "="*80)  
20 - print("测试 1: 标准 Faceting (multi_select=False)")  
21 - print("="*80)  
22 -  
23 - # 选择 category1_name = "玩具"  
24 - payload = {  
25 - "query": "*",  
26 - "size": 5,  
27 - "filters": {  
28 - "category1_name": "玩具"  
29 - },  
30 - "facets": [  
31 - {  
32 - "field": "category1_name",  
33 - "size": 10,  
34 - "type": "terms",  
35 - "multi_select": False # 标准模式  
36 - }  
37 - ]  
38 - }  
39 -  
40 - headers = {"X-Tenant-ID": TENANT_ID}  
41 -  
42 - try:  
43 - response = requests.post(API_URL, json=payload, headers=headers)  
44 - response.raise_for_status()  
45 - result = response.json()  
46 -  
47 - print(f"\n✓ 请求成功 (HTTP {response.status_code})")  
48 - print(f" 总结果数: {result.get('total', 0)}")  
49 -  
50 - if result.get('facets'):  
51 - for facet in result['facets']:  
52 - if facet['field'] == 'category1_name':  
53 - print(f"\n Facet: {facet['field']}")  
54 - print(f" Values 数量: {len(facet['values'])}")  
55 - for val in facet['values']:  
56 - selected_mark = "✓" if val.get('selected') else " "  
57 - print(f" [{selected_mark}] {val['value']}: {val['count']}")  
58 -  
59 - # 验证  
60 - if len(facet['values']) == 1:  
61 - print("\n ✓ 验证通过: multi_select=False 时,只返回选中的值")  
62 - else:  
63 - print("\n ⚠ 警告: multi_select=False 时应只返回一个值")  
64 -  
65 - selected_values = [v['value'] for v in facet['values'] if v.get('selected')]  
66 - if selected_values == ['玩具']:  
67 - print(" ✓ 验证通过: selected 字段正确标记")  
68 - else:  
69 - print(f" ✗ 错误: selected 标记不正确,期望 ['玩具'],实际 {selected_values}")  
70 - else:  
71 - print("\n ⚠ 警告: 没有返回 facets")  
72 -  
73 - except requests.exceptions.RequestException as e:  
74 - print(f"\n✗ 请求失败: {e}")  
75 - except Exception as e:  
76 - print(f"\n✗ 错误: {e}")  
77 -  
78 -  
79 -def test_multi_select_faceting():  
80 - """测试 Multi-Select Faceting (multi_select=True)"""  
81 - print("\n" + "="*80)  
82 - print("测试 2: Multi-Select Faceting (multi_select=True)")  
83 - print("="*80)  
84 -  
85 - # 选择 category1_name = "玩具"  
86 - payload = {  
87 - "query": "*",  
88 - "size": 5,  
89 - "filters": {  
90 - "category1_name": "玩具"  
91 - },  
92 - "facets": [  
93 - {  
94 - "field": "category1_name",  
95 - "size": 10,  
96 - "type": "terms",  
97 - "multi_select": True # Multi-select 模式  
98 - }  
99 - ]  
100 - }  
101 -  
102 - headers = {"X-Tenant-ID": TENANT_ID}  
103 -  
104 - try:  
105 - response = requests.post(API_URL, json=payload, headers=headers)  
106 - response.raise_for_status()  
107 - result = response.json()  
108 -  
109 - print(f"\n✓ 请求成功 (HTTP {response.status_code})")  
110 - print(f" 总结果数: {result.get('total', 0)}")  
111 -  
112 - if result.get('facets'):  
113 - for facet in result['facets']:  
114 - if facet['field'] == 'category1_name':  
115 - print(f"\n Facet: {facet['field']}")  
116 - print(f" Values 数量: {len(facet['values'])}")  
117 - for val in facet['values'][:5]: # 只显示前5个  
118 - selected_mark = "✓" if val.get('selected') else " "  
119 - print(f" [{selected_mark}] {val['value']}: {val['count']}")  
120 -  
121 - # 验证  
122 - if len(facet['values']) > 1:  
123 - print(f"\n ✓ 验证通过: multi_select=True 时,返回多个值 ({len(facet['values'])} 个)")  
124 - else:  
125 - print("\n ✗ 错误: multi_select=True 时应返回多个值")  
126 -  
127 - selected_values = [v['value'] for v in facet['values'] if v.get('selected')]  
128 - if selected_values == ['玩具']:  
129 - print(" ✓ 验证通过: selected 字段正确标记")  
130 - else:  
131 - print(f" ⚠ 警告: selected 标记可能不正确,期望 ['玩具'],实际 {selected_values}")  
132 - else:  
133 - print("\n ⚠ 警告: 没有返回 facets")  
134 -  
135 - except requests.exceptions.RequestException as e:  
136 - print(f"\n✗ 请求失败: {e}")  
137 - except Exception as e:  
138 - print(f"\n✗ 错误: {e}")  
139 -  
140 -  
141 -def test_specifications_multi_select():  
142 - """测试 Specifications 的 Multi-Select Faceting"""  
143 - print("\n" + "="*80)  
144 - print("测试 3: Specifications Multi-Select Faceting")  
145 - print("="*80)  
146 -  
147 - # 选择 specifications.颜色 = "白色"  
148 - payload = {  
149 - "query": "*",  
150 - "size": 5,  
151 - "filters": {  
152 - "specifications": {  
153 - "name": "颜色",  
154 - "value": "白色"  
155 - }  
156 - },  
157 - "facets": [  
158 - {  
159 - "field": "specifications.颜色",  
160 - "size": 10,  
161 - "type": "terms",  
162 - "multi_select": True  
163 - },  
164 - {  
165 - "field": "specifications.尺寸",  
166 - "size": 10,  
167 - "type": "terms",  
168 - "multi_select": False  
169 - }  
170 - ]  
171 - }  
172 -  
173 - headers = {"X-Tenant-ID": TENANT_ID}  
174 -  
175 - try:  
176 - response = requests.post(API_URL, json=payload, headers=headers)  
177 - response.raise_for_status()  
178 - result = response.json()  
179 -  
180 - print(f"\n✓ 请求成功 (HTTP {response.status_code})")  
181 - print(f" 总结果数: {result.get('total', 0)}")  
182 -  
183 - if result.get('facets'):  
184 - for facet in result['facets']:  
185 - print(f"\n Facet: {facet['field']} (multi_select={facet.get('multi_select', 'N/A')})")  
186 - print(f" Values 数量: {len(facet['values'])}")  
187 - for val in facet['values'][:5]:  
188 - selected_mark = "✓" if val.get('selected') else " "  
189 - print(f" [{selected_mark}] {val['value']}: {val['count']}")  
190 -  
191 - # 验证 specifications.颜色  
192 - if facet['field'] == 'specifications.颜色':  
193 - if len(facet['values']) > 1:  
194 - print(f" ✓ 验证通过: multi_select=True,返回多个颜色选项")  
195 - selected_values = [v['value'] for v in facet['values'] if v.get('selected')]  
196 - if '白色' in selected_values:  
197 - print(" ✓ 验证通过: '白色' 被正确标记为 selected")  
198 -  
199 - # 验证 specifications.尺寸(基于白色商品的尺寸分布)  
200 - if facet['field'] == 'specifications.尺寸':  
201 - print(f" ℹ 尺寸分布基于已选的颜色过滤器")  
202 - else:  
203 - print("\n ⚠ 警告: 没有返回 facets")  
204 -  
205 - except requests.exceptions.RequestException as e:  
206 - print(f"\n✗ 请求失败: {e}")  
207 - except Exception as e:  
208 - print(f"\n✗ 错误: {e}")  
209 -  
210 -  
211 -def test_es_query_structure():  
212 - """测试 ES Query 结构(需要 debug=True)"""  
213 - print("\n" + "="*80)  
214 - print("测试 4: ES Query 结构验证 (debug=True)")  
215 - print("="*80)  
216 -  
217 - payload = {  
218 - "query": "手机",  
219 - "size": 1,  
220 - "filters": {  
221 - "category1_name": "电子产品",  
222 - "specifications": {"name": "颜色", "value": "白色"}  
223 - },  
224 - "facets": [  
225 - {  
226 - "field": "category1_name",  
227 - "size": 5,  
228 - "multi_select": True  
229 - },  
230 - {  
231 - "field": "specifications.颜色",  
232 - "size": 5,  
233 - "multi_select": True  
234 - }  
235 - ],  
236 - "debug": True  
237 - }  
238 -  
239 - headers = {"X-Tenant-ID": TENANT_ID}  
240 -  
241 - try:  
242 - response = requests.post(API_URL, json=payload, headers=headers)  
243 - response.raise_for_status()  
244 - result = response.json()  
245 -  
246 - print(f"\n✓ 请求成功")  
247 -  
248 - if result.get('debug_info') and result['debug_info'].get('es_query'):  
249 - es_query = result['debug_info']['es_query']  
250 -  
251 - # 检查 post_filter  
252 - if 'post_filter' in es_query:  
253 - print("\n ✓ ES Query 包含 post_filter:")  
254 - print(f" {json.dumps(es_query['post_filter'], indent=4, ensure_ascii=False)[:200]}...")  
255 - else:  
256 - print("\n ℹ ES Query 不包含 post_filter(可能没有 multi-select 过滤器)")  
257 -  
258 - # 检查 query.bool.filter  
259 - if 'query' in es_query and 'bool' in es_query['query']:  
260 - filters = es_query['query']['bool'].get('filter', [])  
261 - print(f"\n ✓ Query filters 数量: {len(filters)}")  
262 -  
263 - else:  
264 - print("\n ⚠ 警告: debug_info 中没有 es_query")  
265 -  
266 - except requests.exceptions.RequestException as e:  
267 - print(f"\n✗ 请求失败: {e}")  
268 - except Exception as e:  
269 - print(f"\n✗ 错误: {e}")  
270 -  
271 -  
272 -if __name__ == "__main__":  
273 - print("\n" + "="*80)  
274 - print("Multi-Select Faceting 功能测试")  
275 - print("="*80)  
276 - print(f"\nAPI URL: {API_URL}")  
277 - print(f"Tenant ID: {TENANT_ID}")  
278 -  
279 - # 运行测试  
280 - test_standard_faceting()  
281 - test_multi_select_faceting()  
282 - test_specifications_multi_select()  
283 - test_es_query_structure()  
284 -  
285 - print("\n" + "="*80)  
286 - print("测试完成")  
287 - print("="*80)  
288 -