From 985d7fe3445c9ccc53390de172438681849ed042 Mon Sep 17 00:00:00 2001 From: tangwang Date: Fri, 6 Feb 2026 11:23:06 +0800 Subject: [PATCH] 为 filters 中所有字段加上 `*_all` 语义 --- api/models.py | 2 +- docs/常用查询 - ES.md | 43 ++++++++++++++++++++++++++++++++++++++++--- docs/搜索API对接指南.md | 24 ++++++++++++++++-------- search/es_query_builder.py | 41 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 97 insertions(+), 13 deletions(-) diff --git a/api/models.py b/api/models.py index f22e888..484152e 100644 --- a/api/models.py +++ b/api/models.py @@ -81,7 +81,7 @@ class SearchRequest(BaseModel): # 过滤器 - 精确匹配和多值匹配 filters: Optional[Dict[str, Union[str, int, bool, List[Union[str, int]], Dict[str, Any], List[Dict[str, Any]]]]] = Field( None, - description="精确匹配过滤器。单值表示精确匹配,数组表示 OR 匹配(匹配任意一个值)。支持 specifications 嵌套过滤:{\"specifications\": {\"name\": \"color\", \"value\": \"green\"}} 或 [{\"name\": \"color\", \"value\": \"green\"}, ...]", + description="精确匹配过滤器。单值表示精确匹配,数组表示 OR 匹配(匹配任意一个值)。字段名加 _all 后缀表示 AND(如 tags_all: ['A','B'] 表示同时包含 A 和 B)。支持 specifications 嵌套过滤:{\"specifications\": {\"name\": \"color\", \"value\": \"green\"}} 或 [{\"name\": \"color\", \"value\": \"green\"}, ...];specifications_all 表示列表内所有规格条件都要满足。", json_schema_extra={ "examples": [ { diff --git a/docs/常用查询 - ES.md b/docs/常用查询 - ES.md index cd5a7a3..ddf98f4 100644 --- a/docs/常用查询 - ES.md +++ b/docs/常用查询 - ES.md @@ -31,7 +31,44 @@ curl -u 'essa:4hOaLaf41y2VuI8y' -X GET 'http://localhost:9200/search_products/ } }' -curl -u 'essa:4hOaLaf41y2VuI8y' -X GET 'http://localhost:9200/search_products/_search?pretty' -H 'Content-Type: application/json' -d '{ + +curl -u 'essa:4hOaLaf41y2VuI8y' -X GET 'http://localhost:9200/search_products_tenant_170/_search?pretty' -H 'Content-Type: application/json' -d '{ + "size": 5, + "_source": ["title", "keyword", "keyword.zh", "tags"], + "query": { + "bool": { + "filter": [ + { "term": { "spu_id": "223167" } } + ] + } + } + }' + + +curl -u 'essa:4hOaLaf41y2VuI8y' -X GET 'http://localhost:9200/search_products_tenant_170/_search?pretty' -H 'Content-Type: application/json' -d '{ + "size": 1, + "_source": ["title", "keyword", "keyword.zh", "tags"], + "query": { + "bool": { + "must": [ + { + "match": { + "title.en": { + "query": "Floerns Women Gothic Graphic Ribbed Strapless Tube Top Asymmetrical Ruched Bandeau Tops" + } + } + } + ], + "filter": [ + { "term": { "tenant_id": "170" } }, + { "terms": { "tags": ["女装", "派对"] } } + ] + } + } +}' + + +curl -u 'essa:4hOaLaf41y2VuI8y' -X GET 'http://localhost:9200/search_products_tenant_170/_search?pretty' -H 'Content-Type: application/json' -d '{ "size": 1, "_source": ["title"], "query": { @@ -39,8 +76,8 @@ curl -u 'essa:4hOaLaf41y2VuI8y' -X GET 'http://localhost:9200/search_products/ "must": [ { "match": { - "title.zh": { - "query": "裙子" + "title.en": { + "query": "Floerns Women Gothic Graphic Ribbed Strapless Tube Top Asymmetrical Ruched Bandeau Tops" } } } diff --git a/docs/搜索API对接指南.md b/docs/搜索API对接指南.md index 4da4c47..e8511a4 100644 --- a/docs/搜索API对接指南.md +++ b/docs/搜索API对接指南.md @@ -201,18 +201,20 @@ curl -X POST "http://120.76.41.98:6002/search/" \ #### 3.3.1 精确匹配过滤器 (filters) -用于精确匹配或多值匹配。对于普通字段,数组表示 OR 逻辑(匹配任意一个值);对于 specifications 字段,按维度分组处理。 +用于精确匹配或多值匹配。对于普通字段,数组表示 OR 逻辑(匹配任意一个值);对于 specifications 字段,按维度分组处理。**任意字段名加 `_all` 后缀**表示多值 AND 逻辑(必须同时匹配所有值)。 **格式**: ```json { "filters": { - "category_name": "手机", // 可以为单值 或者 数组 匹配数组中任意一个 - "category1_name": "服装", // 可以为单值 或者 数组 匹配数组中任意一个 - "category2_name": "男装", // 可以为单值 或者 数组 匹配数组中任意一个 - "category3_name": "衬衫", // 可以为单值 或者 数组 匹配数组中任意一个 - "vendor.zh.keyword": ["奇乐", "品牌A"], // 可以为单值 或者 数组 匹配数组中任意一个 - "tags": "手机", // 可以为单值 或者 数组 匹配数组中任意一个 + "category_name": "手机", // 可以为单值 或者 数组 匹配数组中任意一个(OR) + "category1_name": "服装", // 可以为单值 或者 数组 匹配数组中任意一个(OR) + "category2_name": "男装", // 可以为单值 或者 数组 匹配数组中任意一个(OR) + "category3_name": "衬衫", // 可以为单值 或者 数组 匹配数组中任意一个(OR) + "vendor.zh.keyword": ["奇乐", "品牌A"], // 可以为单值 或者 数组 匹配数组中任意一个(OR) + "tags": "手机", // 可以为单值 或者 数组 匹配数组中任意一个(OR) + "tags_all": ["手机", "促销", "新品"], // *_all:多值为 AND,必须同时包含所有标签 + "category1_name_all": ["服装", "男装"], // 同上,适用于任意可过滤字段 // specifications 嵌套过滤(特殊格式) "specifications": { "name": "color", @@ -226,9 +228,14 @@ curl -X POST "http://120.76.41.98:6002/search/" \ - 字符串:精确匹配 - 整数:精确匹配 - 布尔值:精确匹配 -- 数组:匹配任意值(OR 逻辑) +- 数组:匹配任意值(OR 逻辑);若字段名以 `_all` 结尾,则数组表示 AND 逻辑(必须同时匹配所有值) - 对象:specifications 嵌套过滤(见下文) +**`*_all` 语义(多值 AND)**: +- 任意过滤字段均可使用 `_all` 后缀,对应 ES 字段名为去掉 `_all` 后的名称。 +- 例如:`tags_all: ["A", "B"]` 表示文档的 `tags` 必须**同时包含** A 和 B;`vendor.zh.keyword_all: ["奇乐", "品牌A"]` 表示同时匹配两个品牌(通常用于 keyword 多值场景)。 +- `specifications_all`:传列表 `[{"name":"color","value":"white"},{"name":"size","value":"256GB"}]` 时,表示所有列出的规格条件都要满足(与 `specifications` 多维度时的 AND 一致;若同维度多值则要求文档同时满足多个值,一般用于嵌套多值场景)。 + **Specifications 嵌套过滤**: `specifications` 是嵌套字段,支持按规格名称和值进行过滤。 @@ -286,6 +293,7 @@ curl -X POST "http://120.76.41.98:6002/search/" \ - `tags`: 标签(keyword类型,支持数组) - `option1_name`, `option2_name`, `option3_name`: 选项名称 - `specifications`: 规格过滤(嵌套字段,格式见上文) +- 以上任意字段均可加 `_all` 后缀表示多值 AND,如 `tags_all`、`category1_name_all`。 #### 3.3.2 范围过滤器 (range_filters) diff --git a/search/es_query_builder.py b/search/es_query_builder.py index 81d60dd..fce5261 100644 --- a/search/es_query_builder.py +++ b/search/es_query_builder.py @@ -712,7 +712,46 @@ class ESQueryBuilder: }) continue - # 普通字段过滤 + # *_all 语义:多值时为 AND(必须同时匹配所有值) + if field.endswith("_all"): + es_field = field[:-4] # 去掉 _all 后缀 + if es_field == "specifications" and isinstance(value, list): + # specifications_all: 列表内每个规格条件都要满足(AND) + must_nested = [] + for spec in value: + if isinstance(spec, dict): + name = spec.get("name") + spec_value = spec.get("value") + if name and spec_value: + must_nested.append({ + "nested": { + "path": "specifications", + "query": { + "bool": { + "must": [ + {"term": {"specifications.name": name}}, + {"term": {"specifications.value": spec_value}} + ] + } + } + } + }) + if must_nested: + filter_clauses.append({"bool": {"must": must_nested}}) + else: + # 普通字段 _all:多值用 must + 多个 term + if isinstance(value, list): + if value: + filter_clauses.append({ + "bool": { + "must": [{"term": {es_field: v}} for v in value] + } + }) + else: + filter_clauses.append({"term": {es_field: value}}) + continue + + # 普通字段过滤(默认多值为 OR) if isinstance(value, list): # 多值匹配(OR) filter_clauses.append({ -- libgit2 0.21.2