diff --git a/docs/multi_select_faceting.md b/docs/multi_select_faceting.md
deleted file mode 100644
index ea57de8..0000000
--- a/docs/multi_select_faceting.md
+++ /dev/null
@@ -1,399 +0,0 @@
-# Multi-Select Faceting 功能说明
-
-## 概述
-
-Multi-Select Faceting(多选分面)是业界标准的 Faceted Search 功能,允许用户在选中某个分面筛选项后,仍然能看到该分面的其他可选项,提供更好的探索式搜索体验。
-
-## 功能特性
-
-### 1. 两种 Faceting 模式
-
-#### 标准模式(Conjunctive Faceting)
-- **设置**: `multi_select: false`(默认)
-- **行为**: 选中某个分面值后,该分面只显示选中的值
-- **适用场景**: 层级下钻、逐步精炼
-- **ES 实现**: 过滤器应用在 `query.bool.filter`
-
-#### Multi-Select 模式(Disjunctive Faceting)
-- **设置**: `multi_select: true`
-- **行为**: 选中某个分面值后,该分面仍显示所有可选项
-- **适用场景**: 颜色、品牌、尺码等可切换属性
-- **ES 实现**: 过滤器应用在 `post_filter`
-
-### 2. Selected 状态标记
-
-所有 facet 值都包含 `selected` 字段,标记当前是否被选中:
-- `selected: true` - 当前筛选项已被选中
-- `selected: false` - 当前筛选项未被选中
-
-## 使用示例
-
-### 示例 1: 标准 Category Faceting
-
-```json
-{
- "query": "T恤",
- "filters": {
- "category1_name": "服装"
- },
- "facets": [
- {
- "field": "category1_name",
- "size": 10,
- "type": "terms",
- "multi_select": false
- }
- ]
-}
-```
-
-**响应**:
-```json
-{
- "results": [...],
- "facets": [
- {
- "field": "category1_name",
- "values": [
- {"value": "服装", "count": 150, "selected": true}
- ]
- }
- ]
-}
-```
-
-### 示例 2: Multi-Select Brand Faceting
-
-```json
-{
- "query": "手机",
- "filters": {
- "brand_name": "苹果"
- },
- "facets": [
- {
- "field": "brand_name",
- "size": 10,
- "type": "terms",
- "multi_select": true
- }
- ]
-}
-```
-
-**响应**:
-```json
-{
- "results": [...只包含苹果手机...],
- "facets": [
- {
- "field": "brand_name",
- "values": [
- {"value": "苹果", "count": 150, "selected": true},
- {"value": "华为", "count": 120, "selected": false},
- {"value": "小米", "count": 98, "selected": false}
- ]
- }
- ]
-}
-```
-
-### 示例 3: Specifications Multi-Select
-
-```json
-{
- "query": "衬衫",
- "filters": {
- "specifications": {
- "name": "颜色",
- "value": "白色"
- }
- },
- "facets": [
- {
- "field": "specifications.颜色",
- "size": 10,
- "type": "terms",
- "multi_select": true
- },
- {
- "field": "specifications.尺码",
- "size": 10,
- "type": "terms",
- "multi_select": false
- }
- ]
-}
-```
-
-**响应**:
-```json
-{
- "results": [...只包含白色衬衫...],
- "facets": [
- {
- "field": "specifications.颜色",
- "label": "颜色",
- "values": [
- {"value": "白色", "count": 50, "selected": true},
- {"value": "蓝色", "count": 35, "selected": false},
- {"value": "黑色", "count": 28, "selected": false}
- ]
- },
- {
- "field": "specifications.尺码",
- "label": "尺码",
- "values": [
- {"value": "M", "count": 20, "selected": false},
- {"value": "L", "count": 18, "selected": false},
- {"value": "XL", "count": 12, "selected": false}
- ]
- }
- ]
-}
-```
-
-注意:尺码分面(`multi_select: false`)的统计是基于白色衬衫的。
-
-### 示例 4: 混合多个 Multi-Select Facets
-
-```json
-{
- "query": "*",
- "filters": {
- "category1_name": "玩具",
- "specifications": [
- {"name": "颜色", "value": "红色"},
- {"name": "材质", "value": "塑料"}
- ]
- },
- "facets": [
- {
- "field": "category1_name",
- "size": 10,
- "multi_select": true
- },
- {
- "field": "specifications.颜色",
- "size": 10,
- "multi_select": true
- },
- {
- "field": "specifications.材质",
- "size": 10,
- "multi_select": true
- },
- {
- "field": "specifications.年龄段",
- "size": 10,
- "multi_select": false
- }
- ]
-}
-```
-
-**行为说明**:
-- `category1_name`: 显示所有类目选项(玩具被标记为 selected)
-- `specifications.颜色`: 显示所有颜色选项(红色被标记为 selected)
-- `specifications.材质`: 显示所有材质选项(塑料被标记为 selected)
-- `specifications.年龄段`: 只显示符合当前过滤条件的年龄段选项
-
-## 前端集成建议
-
-### React 示例
-
-```jsx
-function FacetComponent({ facet }) {
- return (
-
-
{facet.label}
- {facet.values.map(value => (
-
- ))}
-
- );
-}
-```
-
-### Vue 示例
-
-```vue
-
-
-
{{ facet.label }}
-
-
-
-```
-
-## 技术实现细节
-
-### Elasticsearch Query 结构
-
-**Multi-Select Faceting** 使用 `post_filter` 实现:
-
-```json
-{
- "query": {
- "bool": {
- "must": [...],
- "filter": [
- // 只包含 multi_select=false 的过滤器
- {"term": {"category2_name": "短袖T恤"}}
- ]
- }
- },
- "post_filter": {
- "bool": {
- "filter": [
- // 包含 multi_select=true 的过滤器
- {"term": {"brand_name": "苹果"}},
- {
- "nested": {
- "path": "specifications",
- "query": {
- "bool": {
- "must": [
- {"term": {"specifications.name": "颜色"}},
- {"term": {"specifications.value": "白色"}}
- ]
- }
- }
- }
- }
- ]
- }
- },
- "aggs": {
- // 所有聚合都基于 query 的结果(不受 post_filter 影响)
- "brand_name_facet": {...},
- "specifications_颜色_facet": {...}
- }
-}
-```
-
-**关键点**:
-- `query.bool.filter`: 影响结果和聚合
-- `post_filter`: 只影响结果,不影响聚合
-- 聚合统计基于 `query` 的结果,因此 multi-select facet 可以显示多个选项
-
-## 最佳实践
-
-### 1. 何时使用 Multi-Select
-
-| Facet 类型 | 推荐模式 | 原因 |
-|-----------|---------|------|
-| 颜色 | `multi_select: true` | 用户需要切换颜色 |
-| 品牌 | `multi_select: true` | 用户需要比较不同品牌 |
-| 尺码 | `multi_select: true` | 用户需要查看其他尺码 |
-| 类目 | `multi_select: false` | 层级下钻 |
-| 价格区间 | `multi_select: false` | 互斥选择 |
-| 是否有货 | `multi_select: false` | 布尔值筛选 |
-
-### 2. 性能考虑
-
-- **过多 Multi-Select**: 会增加 ES 查询复杂度
-- **建议**: 最多 3-5 个 multi-select facets
-- **优化**: 对于不常用的属性使用标准模式
-
-### 3. UI 设计建议
-
-- **Multi-Select Facets**: 使用复选框(Checkbox)
-- **Standard Facets**: 使用单选框(Radio)或链接
-- **Selected 状态**: 使用不同颜色或图标标识
-
-## API 变更说明
-
-### 新增字段
-
-**FacetConfig**:
-```json
-{
- "field": "brand_name",
- "size": 10,
- "type": "terms",
- "multi_select": true // 新增字段
-}
-```
-
-**FacetValue**:
-```json
-{
- "value": "苹果",
- "count": 150,
- "selected": true // 现在由后端返回真实状态
-}
-```
-
-### 兼容性
-
-- `multi_select` 默认为 `false`,保持向后兼容
-- 旧版 API 调用仍然有效(使用标准模式)
-
-## 测试验证
-
-运行测试脚本:
-```bash
-python test_multi_select_facet.py
-```
-
-测试覆盖:
-1. ✓ 标准 Faceting (multi_select=false)
-2. ✓ Multi-Select Faceting (multi_select=true)
-3. ✓ Specifications Multi-Select
-4. ✓ ES Query 结构验证
-
-## 故障排查
-
-### 问题 1: Multi-Select 不生效
-
-**症状**: 设置了 `multi_select: true`,但仍然只返回一个值
-
-**检查**:
-1. 确认 `multi_select` 字段在请求中正确设置
-2. 检查 ES query 是否包含 `post_filter`(开启 `debug: true`)
-3. 验证 Elasticsearch 版本支持 `post_filter`
-
-### 问题 2: Selected 标记不正确
-
-**症状**: `selected` 字段没有正确标记
-
-**检查**:
-1. 确认 `filters` 中的字段名与 facet 字段名一致
-2. 对于 specifications,检查 `name` 和 `value` 是否匹配
-3. 检查 `filters` 的值类型(字符串、数组等)
-
-### 问题 3: 性能问题
-
-**症状**: 启用 Multi-Select 后查询变慢
-
-**优化**:
-1. 减少 multi-select facets 数量
-2. 降低 facet `size` 参数
-3. 考虑使用缓存
-4. 为常用字段建立索引
-
-## 参考资料
-
-- [Elasticsearch Post Filter](https://www.elastic.co/guide/en/elasticsearch/reference/current/filter-search-results.html#post-filter)
-- [Algolia Disjunctive Faceting](https://www.algolia.com/doc/guides/managing-results/refine-results/faceting/#conjunctive-and-disjunctive-facets)
-- [Amazon Product Search](https://www.amazon.com) - 业界最佳实践示例
-
diff --git a/docs/搜索API对接指南.md b/docs/搜索API对接指南.md
index a28df7e..6c42cab 100644
--- a/docs/搜索API对接指南.md
+++ b/docs/搜索API对接指南.md
@@ -318,38 +318,17 @@ curl -X POST "http://120.76.41.98:6002/search/" \
**重要特性**: `multi_select` 字段控制分面的行为模式。
+
##### 标准模式 (multi_select: false)
- **行为**: 选中某个分面值后,该分面只显示选中的值
- **适用场景**: 层级类目、互斥选择
- **示例**: 类目下钻(玩具 > 娃娃 > 芭比)
-```json
-{
- "filters": {"category1_name": "玩具"},
- "facets": [
- {"field": "category1_name", "size": 10, "multi_select": false}
- ]
-}
-```
-**响应**: 只返回 "玩具" 一个选项
-
##### Multi-Select 模式 (multi_select: true) ⭐
- **行为**: 选中某个分面值后,该分面仍显示所有可选项
- **适用场景**: 颜色、品牌、尺码等可切换属性
- **示例**: 选择了"红色"后,仍能看到"蓝色"、"绿色"等选项
-```json
-{
- "filters": {
- "specifications": {"name": "颜色", "value": "红色"}
- },
- "facets": [
- {"field": "specifications.颜色", "size": 10, "multi_select": true}
- ]
-}
-```
-**响应**: 返回所有颜色选项,"红色" 被标记为 `selected: true`
-
##### 推荐配置
| 分面类型 | multi_select | 原因 |
diff --git a/frontend/index.html b/frontend/index.html
index 27001c6..81583f1 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -23,7 +23,7 @@
-
+
diff --git a/test_multi_select_facet.py b/test_multi_select_facet.py
deleted file mode 100644
index 6bde77a..0000000
--- a/test_multi_select_facet.py
+++ /dev/null
@@ -1,288 +0,0 @@
-#!/usr/bin/env python3
-"""
-测试 Multi-Select Faceting 和 Selected 标记功能
-
-验证:
-1. multi_select=False 时,选中某个 facet 值后,该 facet 只返回一个值(标准模式)
-2. multi_select=True 时,选中某个 facet 值后,该 facet 仍返回多个值(disjunctive 模式)
-3. selected 字段正确标记
-"""
-
-import requests
-import json
-
-API_URL = "http://localhost:8000/api/search"
-TENANT_ID = "test_tenant"
-
-def test_standard_faceting():
- """测试标准 faceting(multi_select=False)"""
- print("\n" + "="*80)
- print("测试 1: 标准 Faceting (multi_select=False)")
- print("="*80)
-
- # 选择 category1_name = "玩具"
- payload = {
- "query": "*",
- "size": 5,
- "filters": {
- "category1_name": "玩具"
- },
- "facets": [
- {
- "field": "category1_name",
- "size": 10,
- "type": "terms",
- "multi_select": False # 标准模式
- }
- ]
- }
-
- headers = {"X-Tenant-ID": TENANT_ID}
-
- try:
- response = requests.post(API_URL, json=payload, headers=headers)
- response.raise_for_status()
- result = response.json()
-
- print(f"\n✓ 请求成功 (HTTP {response.status_code})")
- print(f" 总结果数: {result.get('total', 0)}")
-
- if result.get('facets'):
- for facet in result['facets']:
- if facet['field'] == 'category1_name':
- print(f"\n Facet: {facet['field']}")
- print(f" Values 数量: {len(facet['values'])}")
- for val in facet['values']:
- selected_mark = "✓" if val.get('selected') else " "
- print(f" [{selected_mark}] {val['value']}: {val['count']}")
-
- # 验证
- if len(facet['values']) == 1:
- print("\n ✓ 验证通过: multi_select=False 时,只返回选中的值")
- else:
- print("\n ⚠ 警告: multi_select=False 时应只返回一个值")
-
- selected_values = [v['value'] for v in facet['values'] if v.get('selected')]
- if selected_values == ['玩具']:
- print(" ✓ 验证通过: selected 字段正确标记")
- else:
- print(f" ✗ 错误: selected 标记不正确,期望 ['玩具'],实际 {selected_values}")
- else:
- print("\n ⚠ 警告: 没有返回 facets")
-
- except requests.exceptions.RequestException as e:
- print(f"\n✗ 请求失败: {e}")
- except Exception as e:
- print(f"\n✗ 错误: {e}")
-
-
-def test_multi_select_faceting():
- """测试 Multi-Select Faceting (multi_select=True)"""
- print("\n" + "="*80)
- print("测试 2: Multi-Select Faceting (multi_select=True)")
- print("="*80)
-
- # 选择 category1_name = "玩具"
- payload = {
- "query": "*",
- "size": 5,
- "filters": {
- "category1_name": "玩具"
- },
- "facets": [
- {
- "field": "category1_name",
- "size": 10,
- "type": "terms",
- "multi_select": True # Multi-select 模式
- }
- ]
- }
-
- headers = {"X-Tenant-ID": TENANT_ID}
-
- try:
- response = requests.post(API_URL, json=payload, headers=headers)
- response.raise_for_status()
- result = response.json()
-
- print(f"\n✓ 请求成功 (HTTP {response.status_code})")
- print(f" 总结果数: {result.get('total', 0)}")
-
- if result.get('facets'):
- for facet in result['facets']:
- if facet['field'] == 'category1_name':
- print(f"\n Facet: {facet['field']}")
- print(f" Values 数量: {len(facet['values'])}")
- for val in facet['values'][:5]: # 只显示前5个
- selected_mark = "✓" if val.get('selected') else " "
- print(f" [{selected_mark}] {val['value']}: {val['count']}")
-
- # 验证
- if len(facet['values']) > 1:
- print(f"\n ✓ 验证通过: multi_select=True 时,返回多个值 ({len(facet['values'])} 个)")
- else:
- print("\n ✗ 错误: multi_select=True 时应返回多个值")
-
- selected_values = [v['value'] for v in facet['values'] if v.get('selected')]
- if selected_values == ['玩具']:
- print(" ✓ 验证通过: selected 字段正确标记")
- else:
- print(f" ⚠ 警告: selected 标记可能不正确,期望 ['玩具'],实际 {selected_values}")
- else:
- print("\n ⚠ 警告: 没有返回 facets")
-
- except requests.exceptions.RequestException as e:
- print(f"\n✗ 请求失败: {e}")
- except Exception as e:
- print(f"\n✗ 错误: {e}")
-
-
-def test_specifications_multi_select():
- """测试 Specifications 的 Multi-Select Faceting"""
- print("\n" + "="*80)
- print("测试 3: Specifications Multi-Select Faceting")
- print("="*80)
-
- # 选择 specifications.颜色 = "白色"
- payload = {
- "query": "*",
- "size": 5,
- "filters": {
- "specifications": {
- "name": "颜色",
- "value": "白色"
- }
- },
- "facets": [
- {
- "field": "specifications.颜色",
- "size": 10,
- "type": "terms",
- "multi_select": True
- },
- {
- "field": "specifications.尺寸",
- "size": 10,
- "type": "terms",
- "multi_select": False
- }
- ]
- }
-
- headers = {"X-Tenant-ID": TENANT_ID}
-
- try:
- response = requests.post(API_URL, json=payload, headers=headers)
- response.raise_for_status()
- result = response.json()
-
- print(f"\n✓ 请求成功 (HTTP {response.status_code})")
- print(f" 总结果数: {result.get('total', 0)}")
-
- if result.get('facets'):
- for facet in result['facets']:
- print(f"\n Facet: {facet['field']} (multi_select={facet.get('multi_select', 'N/A')})")
- print(f" Values 数量: {len(facet['values'])}")
- for val in facet['values'][:5]:
- selected_mark = "✓" if val.get('selected') else " "
- print(f" [{selected_mark}] {val['value']}: {val['count']}")
-
- # 验证 specifications.颜色
- if facet['field'] == 'specifications.颜色':
- if len(facet['values']) > 1:
- print(f" ✓ 验证通过: multi_select=True,返回多个颜色选项")
- selected_values = [v['value'] for v in facet['values'] if v.get('selected')]
- if '白色' in selected_values:
- print(" ✓ 验证通过: '白色' 被正确标记为 selected")
-
- # 验证 specifications.尺寸(基于白色商品的尺寸分布)
- if facet['field'] == 'specifications.尺寸':
- print(f" ℹ 尺寸分布基于已选的颜色过滤器")
- else:
- print("\n ⚠ 警告: 没有返回 facets")
-
- except requests.exceptions.RequestException as e:
- print(f"\n✗ 请求失败: {e}")
- except Exception as e:
- print(f"\n✗ 错误: {e}")
-
-
-def test_es_query_structure():
- """测试 ES Query 结构(需要 debug=True)"""
- print("\n" + "="*80)
- print("测试 4: ES Query 结构验证 (debug=True)")
- print("="*80)
-
- payload = {
- "query": "手机",
- "size": 1,
- "filters": {
- "category1_name": "电子产品",
- "specifications": {"name": "颜色", "value": "白色"}
- },
- "facets": [
- {
- "field": "category1_name",
- "size": 5,
- "multi_select": True
- },
- {
- "field": "specifications.颜色",
- "size": 5,
- "multi_select": True
- }
- ],
- "debug": True
- }
-
- headers = {"X-Tenant-ID": TENANT_ID}
-
- try:
- response = requests.post(API_URL, json=payload, headers=headers)
- response.raise_for_status()
- result = response.json()
-
- print(f"\n✓ 请求成功")
-
- if result.get('debug_info') and result['debug_info'].get('es_query'):
- es_query = result['debug_info']['es_query']
-
- # 检查 post_filter
- if 'post_filter' in es_query:
- print("\n ✓ ES Query 包含 post_filter:")
- print(f" {json.dumps(es_query['post_filter'], indent=4, ensure_ascii=False)[:200]}...")
- else:
- print("\n ℹ ES Query 不包含 post_filter(可能没有 multi-select 过滤器)")
-
- # 检查 query.bool.filter
- if 'query' in es_query and 'bool' in es_query['query']:
- filters = es_query['query']['bool'].get('filter', [])
- print(f"\n ✓ Query filters 数量: {len(filters)}")
-
- else:
- print("\n ⚠ 警告: debug_info 中没有 es_query")
-
- except requests.exceptions.RequestException as e:
- print(f"\n✗ 请求失败: {e}")
- except Exception as e:
- print(f"\n✗ 错误: {e}")
-
-
-if __name__ == "__main__":
- print("\n" + "="*80)
- print("Multi-Select Faceting 功能测试")
- print("="*80)
- print(f"\nAPI URL: {API_URL}")
- print(f"Tenant ID: {TENANT_ID}")
-
- # 运行测试
- test_standard_faceting()
- test_multi_select_faceting()
- test_specifications_multi_select()
- test_es_query_structure()
-
- print("\n" + "="*80)
- print("测试完成")
- print("="*80)
-
--
libgit2 0.21.2