# 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 ``` ## 技术实现细节 ### 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) - 业界最佳实践示例