Blame view

docs/multi_select_faceting.md 8.98 KB
c581becd   tangwang   feat: 实现 Multi-Se...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
  # 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 (
      <div className="facet">
        <h3>{facet.label}</h3>
        {facet.values.map(value => (
          <label key={value.value} className={value.selected ? 'active' : ''}>
            <input 
              type="checkbox"
              checked={value.selected}
              onChange={() => toggleFilter(facet.field, value.value)}
            />
            {value.value} ({value.count})
          </label>
        ))}
      </div>
    );
  }
  ```
  
  ### Vue 示例
  
  ```vue
  <template>
    <div class="facet">
      <h3>{{ facet.label }}</h3>
      <label 
        v-for="value in facet.values" 
        :key="value.value"
        :class="{ active: value.selected }"
      >
        <input 
          type="checkbox"
          :checked="value.selected"
          @change="toggleFilter(facet.field, value.value)"
        />
        {{ value.value }} ({{ value.count }})
      </label>
    </div>
  </template>
  ```
  
  ## 技术实现细节
  
  ### 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) - 业界最佳实践示例