# 搜索API接口对接指南 本文档为搜索服务的使用方提供完整的API对接指南,包括接口说明、请求参数、响应格式和使用示例。 ## 目录 1. [快速开始](#快速开始) - 1.1 [基础信息](#11-基础信息) - 1.2 [最简单的搜索请求](#12-最简单的搜索请求) - 1.3 [带过滤与分页的搜索](#13-带过滤与分页的搜索) - 1.4 [开启分面的搜索](#14-开启分面的搜索) 2. [接口概览](#接口概览) 3. [搜索接口](#搜索接口) - 3.1 [接口信息](#31-接口信息) - 3.2 [请求参数](#32-请求参数) - 3.3 [过滤器详解](#33-过滤器详解) - 3.4 [分面配置](#34-分面配置) - 3.5 [SKU筛选维度](#35-sku筛选维度) - 3.6 [搜索建议接口](#37-搜索建议接口) - 3.7 [即时搜索接口](#38-即时搜索接口) - 3.8 [获取单个文档](#39-获取单个文档) 4. [响应格式说明](#响应格式说明) - 4.1 [标准响应结构](#41-标准响应结构) - 4.2 [响应字段说明](#42-响应字段说明) - 4.2.1 [query_info 说明](#421-query_info-说明) - 4.3 [SpuResult字段说明](#43-spuresult字段说明) - 4.4 [SkuResult字段说明](#44-skuresult字段说明) - 4.5 [多语言字段说明](#45-多语言字段说明) 5. [索引接口](#索引接口) - 5.0 [为租户创建索引](#50-为租户创建索引) - 5.1 [全量索引接口](#51-全量索引接口) - 5.2 [增量索引接口](#52-增量索引接口) - 5.3 [查询文档接口](#53-查询文档接口) - 5.4 [索引健康检查接口](#54-索引健康检查接口) - 5.5 [文档构建接口(正式对接)](#55-文档构建接口正式对接推荐) - 5.6 [文档构建接口(测试/自测)](#56-文档构建接口测试--自测) 6. [管理接口](#管理接口) - 6.1 [健康检查](#61-健康检查) - 6.2 [获取配置](#62-获取配置) - 6.3 [索引统计](#63-索引统计) 7. [微服务接口(向量、重排、翻译)](#7-微服务接口向量重排翻译) - 7.1 [向量服务(Embedding)](#71-向量服务embedding) - 7.2 [重排服务(Reranker)](#72-重排服务reranker) - 7.3 [翻译服务(Translation)](#73-翻译服务translation) 8. [常见场景示例](#8-常见场景示例) - 8.1 [基础搜索与排序](#81-基础搜索与排序) - 8.2 [过滤搜索](#82-过滤搜索) - 8.3 [分面搜索](#83-分面搜索) - 8.4 [规格过滤与分面](#84-规格过滤与分面) - 8.5 [SKU筛选](#85-sku筛选) - 8.6 [分页查询](#87-分页查询) 9. [数据模型](#9-数据模型) - 9.1 [商品字段定义](#91-商品字段定义) - 9.2 [字段类型速查](#92-字段类型速查) - 9.3 [常用字段列表](#93-常用字段列表) - 9.4 [支持的分析器](#94-支持的分析器) --- ## 快速开始 ### 1.1 基础信息 - **Base URL**: `http://43.166.252.75:6002` - **协议**: HTTP/HTTPS - **数据格式**: JSON - **字符编码**: UTF-8 - **请求方法**: POST(搜索接口) **重要提示**: `tenant_id` 通过 HTTP Header `X-Tenant-ID` 传递,不在请求体中。 **环境与凭证**:MySQL、Redis、Elasticsearch 等外部服务的 AI 生产地址与凭证见 [QUICKSTART.md §1.6](./QUICKSTART.md#16-外部服务与-env含生产凭证)。 ### 1.2 最简单的搜索请求 ```bash curl -X POST "http://43.166.252.75:6002/search/" \ -H "Content-Type: application/json" \ -H "X-Tenant-ID: 162" \ -d '{"query": "芭比娃娃"}' ``` ### 1.3 带过滤与分页的搜索 ```bash curl -X POST "http://43.166.252.75:6002/search/" \ -H "Content-Type: application/json" \ -H "X-Tenant-ID: 162" \ -d '{ "query": "芭比娃娃", "size": 5, "from": 10, "range_filters": { "min_price": { "gte": 50, "lte": 200 }, "create_time": { "gte": "2020-01-01T00:00:00Z" } }, "sort_by": "price", "sort_order": "asc" }' ``` ### 1.4 开启分面的搜索 ```bash curl -X POST "http://43.166.252.75:6002/search/" \ -H "Content-Type: application/json" \ -H "X-Tenant-ID: 162" \ -d '{ "query": "芭比娃娃", "facets": [ {"field": "category1_name", "size": 10, "type": "terms"}, {"field": "specifications.color", "size": 10, "type": "terms"}, {"field": "specifications.size", "size": 10, "type": "terms"} ], "min_score": 0.2 }' ``` --- ## 接口概览 | 接口 | HTTP Method | Endpoint | 说明 | |------|------|------|------| | 搜索 | POST | `/search/` | 执行搜索查询 | | 搜索建议 | GET | `/search/suggestions` | 搜索建议(自动补全/热词,多语言 + 结果直达) | | 即时搜索 | GET | `/search/instant` | 即时搜索预留接口(当前返回 `501 Not Implemented`) | | 获取文档 | GET | `/search/{doc_id}` | 获取单个文档 | | 全量索引 | POST | `/indexer/reindex` | 全量索引接口(导入数据,不删除索引,仅推荐自测使用) | | 增量索引 | POST | `/indexer/index` | 增量索引接口(指定SPU ID列表进行索引,支持自动检测删除和显式删除,仅推荐自测使用) | | 查询文档 | POST | `/indexer/documents` | 查询SPU文档数据(不写入ES) | | 构建ES文档(正式对接) | POST | `/indexer/build-docs` | 基于上游提供的 MySQL 行数据构建 ES doc,不写入 ES,供 Java 等调用后自行写入 | | 构建ES文档(测试用) | POST | `/indexer/build-docs-from-db` | 仅在测试/调试时使用,根据 `tenant_id + spu_ids` 内部查库并构建 ES doc | | 索引健康检查 | GET | `/indexer/health` | 检查索引服务状态 | | 健康检查 | GET | `/admin/health` | 服务健康检查 | | 获取配置 | GET | `/admin/config` | 获取租户配置 | | 索引统计 | GET | `/admin/stats` | 获取租户索引统计信息(需 tenant_id) | **微服务(独立端口,外部可直连)**: | 服务 | 端口 | 接口 | 说明 | |------|------|------|------| | 向量服务 | 6005 | `POST /embed/text` | 文本向量化 | | 向量服务 | 6005 | `POST /embed/image` | 图片向量化 | | 翻译服务 | 6006 | `POST /translate` | 文本翻译(Qwen/DeepL) | | 重排服务 | 6007 | `POST /rerank` | 检索结果重排 | --- ## 搜索接口 ### 3.1 接口信息 - **端点**: `POST /search/` - **描述**: 执行文本搜索查询,支持多语言、过滤器和分面搜索 - **租户标识**:`tenant_id` 通过 HTTP 请求头 **`X-Tenant-ID`** 传递(推荐);也可通过 URL query 参数 **`tenant_id`** 传递。**不要放在请求体中。** **请求示例(推荐)**: ```python url = f"{base_url.rstrip('/')}/search/" headers = { "Content-Type": "application/json", "X-Tenant-ID": "162", # 租户ID,必填 } response = requests.post(url, headers=headers, json={"query": "芭比娃娃"}) ``` ### 3.2 请求参数 #### 完整请求体结构 ```json { "query": "string (required)", "size": 10, "from": 0, "language": "zh", "filters": {}, "range_filters": {}, "facets": [], "sort_by": "string", "sort_order": "desc", "min_score": 0.0, "sku_filter_dimension": ["string"], "debug": false, "enable_rerank": false, "rerank_query_template": "{query}", "rerank_doc_template": "{title}", "user_id": "string", "session_id": "string" } ``` #### 参数详细说明 | 参数 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | `query` | string | Y | - | 搜索查询字符串(统一文本检索策略) | | `size` | integer | N | 10 | 返回结果数量(1-100) | | `from` | integer | N | 0 | 分页偏移量(用于分页) | | `language` | string | N | "zh" | 返回语言:`zh`(中文)或 `en`(英文)。后端会根据此参数选择对应的中英文字段返回 | | `filters` | object | N | null | 精确匹配过滤器(见[过滤器详解](#33-过滤器详解)) | | `range_filters` | object | N | null | 数值范围过滤器(见[过滤器详解](#33-过滤器详解)) | | `facets` | array | N | null | 分面配置(见[分面配置](#34-分面配置)) | | `sort_by` | string | N | null | 排序字段名。支持:`price`(价格)、`sales`(销量)、`create_time`(创建时间)、`update_time`(更新时间)。默认按相关性排序 | | `sort_order` | string | N | "desc" | 排序方向:`asc`(升序)或 `desc`(降序)。注意:`price`+`asc`=价格从低到高,`price`+`desc`=价格从高到低(后端自动映射为min_price或max_price) | | `min_score` | float | N | null | 最小相关性分数阈值 | | `sku_filter_dimension` | array[string] | N | null | 子SKU筛选维度列表(见[SKU筛选维度](#35-sku筛选维度)) | | `debug` | boolean | N | false | 是否返回调试信息 | | `enable_rerank` | boolean | N | false | 是否开启重排(调用外部重排服务对 ES 结果进行二次排序)。开启后若 `from+size<=rerank_window` 才会触发重排 | | `rerank_query_template` | string | N | null | 重排 query 模板(可选)。支持 `{query}` 占位符;不传则使用服务端配置 | | `rerank_doc_template` | string | N | null | 重排 doc 模板(可选)。支持 `{title} {brief} {vendor} {description} {category_path}`;不传则使用服务端配置 | | `user_id` | string | N | null | 用户ID(用于个性化,预留) | | `session_id` | string | N | null | 会话ID(用于分析,预留) | ### 3.3 过滤器详解 #### 3.3.1 精确匹配过滤器 (filters) 用于精确匹配或多值匹配。对于普通字段,数组表示 OR 逻辑(匹配任意一个值);对于 specifications 字段,按维度分组处理。**任意字段名加 `_all` 后缀**表示多值 AND 逻辑(必须同时匹配所有值)。 **格式**: ```json { "filters": { "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", "value": "white" } } } ``` **支持的值类型**: - 字符串:精确匹配 - 整数:精确匹配 - 布尔值:精确匹配 - 数组:匹配任意值(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` 是嵌套字段,支持按规格名称和值进行过滤。 **单个规格过滤**: ```json { "filters": { "specifications": { "name": "color", "value": "white" } } } ``` 查询规格名称为"color"且值为"white"的商品。 **多个规格过滤(按维度分组)**: ```json { "filters": { "specifications": [ {"name": "color", "value": "white"}, {"name": "size", "value": "256GB"} ] } } ``` 查询同时满足所有规格的商品(color=white **且** size=256GB)。 **相同维度的多个值(OR 逻辑)**: ```json { "filters": { "specifications": [ {"name": "size", "value": "3"}, {"name": "size", "value": "4"}, {"name": "size", "value": "5"}, {"name": "color", "value": "green"} ] } } ``` 查询满足 (size=3 **或** size=4 **或** size=5) **且** color=green 的商品。 **过滤逻辑说明**: - **不同维度**(不同的 `name`)之间是 **AND** 关系(求交集) - **相同维度**(相同的 `name`)的多个值之间是 **OR** 关系(求并集) **常用过滤字段**(详见[常用字段列表](#83-常用字段列表)): - `category_name`: 类目名称 - `category1_name`, `category2_name`, `category3_name`: 多级类目 - `category_id`: 类目ID - `vendor.zh.keyword`, `vendor.en.keyword`: 供应商/品牌(使用keyword子字段) - `tags`: 标签(keyword类型,支持数组) - `option1_name`, `option2_name`, `option3_name`: 选项名称 - `specifications`: 规格过滤(嵌套字段,格式见上文) - 以上任意字段均可加 `_all` 后缀表示多值 AND,如 `tags_all`、`category1_name_all`。 #### 3.3.2 范围过滤器 (range_filters) 用于数值字段的范围过滤。 **格式**: ```json { "range_filters": { "min_price": { "gte": 50, // 大于等于 "lte": 200 // 小于等于 }, "max_price": { "gt": 100 // 大于 }, "create_time": { "gte": "2024-01-01T00:00:00Z" // 日期时间字符串 } } } ``` **支持的操作符**: - `gte`: 大于等于 (>=) - `gt`: 大于 (>) - `lte`: 小于等于 (<=) - `lt`: 小于 (<) **注意**: 至少需要指定一个操作符。 **常用范围字段**(详见[常用字段列表](#83-常用字段列表)): - `min_price`: 最低价格 - `max_price`: 最高价格 - `compare_at_price`: 原价 - `create_time`: 创建时间 - `update_time`: 更新时间 ### 3.4 分面配置 用于生成分面统计(分组聚合),常用于构建筛选器UI。 #### 3.4.1 配置格式 ```json { "facets": [ { "field": "category1_name", "size": 15, "type": "terms", "disjunctive": false }, { "field": "brand_name", "size": 10, "type": "terms", "disjunctive": true }, { "field": "specifications.color", "size": 20, "type": "terms", "disjunctive": true }, { "field": "min_price", "type": "range", "ranges": [ {"key": "0-50", "to": 50}, {"key": "50-100", "from": 50, "to": 100}, {"key": "100-200", "from": 100, "to": 200}, {"key": "200+", "from": 200} ] } ] } ``` #### 3.4.2 Facet 字段说明 | 字段 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | `field` | string | 是 | - | 分面字段名 | | `size` | int | 否 | 10 | 返回的分面值数量(1-100) | | `type` | string | 否 | "terms" | 分面类型:`terms`(词条聚合)或 `range`(范围聚合) | | `disjunctive` | bool | 否 | false | 是否支持多选(disjunctive faceting)。启用后,选中该分面的过滤器时,仍会显示其他可选项 | | `ranges` | array | 否 | null | 范围配置(仅 `type="range"` 时需要) | #### 3.4.3 disjunctive字段说明 **重要特性**: `disjunctive` 字段控制分面的行为模式。启用后,选中该分面的过滤器时,仍会显示其他可选项 **标准模式 (disjunctive: false)**: - **行为**: 选中某个分面值后,该分面只显示选中的值 - **适用场景**: 层级类目、互斥选择 - **示例**: 类目下钻(玩具 > 娃娃 > 芭比) **Multi-Select 模式 (disjunctive: true)** ⭐: - **行为**: 选中某个分面值后,该分面仍显示所有可选项 - **适用场景**: 颜色、品牌、尺码等可切换属性 - **示例**: 选择了"红色"后,仍能看到"蓝色"、"绿色"等选项 **推荐配置**: | 分面类型 | disjunctive | 原因 | |---------|-------------|------| | 颜色 | `true` | 用户需要切换颜色 | | 品牌 | `true` | 用户需要比较品牌 | | 尺码 | `true` | 用户需要查看其他尺码 | | 类目 | `false` | 层级下钻 | | 价格区间 | `false` | 互斥选择 | #### 3.4.4 规格分面说明 `specifications` 是嵌套字段,支持两种分面模式: **模式1:所有规格名称的分面**: ```json { "facets": [ { "field": "specifications", "size": 10, "type": "terms" } ] } ``` 返回所有规格名称(name)及其对应的值(value)列表。每个 name 会生成一个独立的分面结果。 **模式2:指定规格名称的分面**: ```json { "facets": [ { "field": "specifications.color", "size": 20, "type": "terms", "disjunctive": true }, { "field": "specifications.size", "size": 15, "type": "terms", "disjunctive": true } ] } ``` 只返回指定规格名称的值列表。格式:`specifications.{name}`,其中 `{name}` 是规格名称(如"color"、"size"、"material")。 **返回格式示例**: ```json { "facets": [ { "field": "specifications.color", "label": "color", "type": "terms", "values": [ {"value": "white", "count": 50, "selected": true}, // ✓ selected 字段由后端标记 {"value": "black", "count": 30, "selected": false}, {"value": "red", "count": 20, "selected": false} ] }, { "field": "specifications.size", "label": "size", "type": "terms", "values": [ {"value": "256GB", "count": 40, "selected": false}, {"value": "512GB", "count": 20, "selected": false} ] } ] } ``` ### 3.5 SKU筛选维度 **功能说明**: `sku_filter_dimension` 用于控制搜索列表页中 **每个 SPU 下方可切换的子款式(子 SKU)维度**,为字符串列表。 在店铺的 **主题装修配置** 中,商家可以为店铺设置一个或多个子款式筛选维度(例如 `color`、`size`),前端列表页会在每个 SPU 下展示这些维度对应的子 SKU 列表,用户可以通过点击不同维度值(如不同颜色)来切换展示的子款式。 当指定 `sku_filter_dimension` 后,后端会根据店铺的这项配置,从所有 SKU 中筛选出这些维度组合对应的子 SKU 数据:系统会按指定维度**组合**对 SKU 进行分组,每个维度组合只返回第一个 SKU(从简实现,选择该组合下的第一款),其余不在这些维度组合中的子 SKU 将不返回。 **支持的维度值**: 1. **直接选项字段**: `option1`、`option2`、`option3` - 直接使用对应的 `option1_value`、`option2_value`、`option3_value` 字段进行分组 2. **规格/选项名称**: 通过 `option1_name`、`option2_name`、`option3_name` 匹配 - 例如:如果 `option1_name` 为 `"color"`,则可以使用 `sku_filter_dimension: ["color"]` 来按颜色分组 **示例**: **按颜色筛选(假设 option1_name = "color")**: ```json { "query": "芭比娃娃", "sku_filter_dimension": ["color"] } ``` **按选项1筛选**: ```json { "query": "芭比娃娃", "sku_filter_dimension": ["option1"] } ``` **按颜色 + 尺寸组合筛选(假设 option1_name = "color", option2_name = "size")**: ```json { "query": "芭比娃娃", "sku_filter_dimension": ["color", "size"] } ``` ### 3.7 搜索建议接口 - **端点**: `GET /search/suggestions` - **描述**: 返回搜索建议(自动补全/热词),支持多语言与“结果直达”(每条 suggestion 附带商品列表)。 #### 查询参数 | 参数 | 类型 | 必填 | 默认值 | 描述 | |------|------|------|--------|------| | `q` | string | Y | - | 查询字符串(至少 1 个字符) | | `size` | integer | N | 10 | 返回建议数量(1-200) | | `language` | string | N | `en` | 请求语言,如 `zh` / `en` / `ar` / `ru`,用于路由到对应语种 suggestion 索引 | | `with_results` | bool | N | `true` | 是否为每条 suggestion 返回商品列表(结果直达) | | `result_size` | integer | N | 3 | 每条 suggestion 返回的商品数量(1-10) | | `debug` | bool | N | `false` | 是否开启调试(目前主要用于排查 suggestion 排序与语言解析) | > **租户标识**:同 [3.1](#31-接口信息),通过请求头 `X-Tenant-ID` 或 query 参数 `tenant_id` 传递。 #### 响应示例 ```json { "query": "iph", "language": "en", "resolved_language": "en", "suggestions": [ { "text": "iphone 15", "lang": "en", "score": 12.37, "rank_score": 5.1, "sources": ["query_log", "qanchor"], "lang_source": "log_field", "lang_confidence": 1.0, "lang_conflict": false, "products": [ { "spu_id": "12345", "title": "iPhone 15 Pro Max", "price": 999.0, "image_url": "https://example.com/image.jpg", "score": 3.21 } ] } ], "took_ms": 12 } ``` #### 请求示例 ```bash curl "http://localhost:6002/search/suggestions?q=芭&size=5&language=zh&with_results=true" \ -H "X-Tenant-ID: 162" ``` ### 3.8 即时搜索接口 > ⚠️ 当前版本未开放该能力。接口会明确返回 `501 Not Implemented`,避免误用未完成实现。 - **端点**: `GET /search/instant` - **描述**: 即时搜索预留端点,后续会在独立实现完成后开放。 #### 查询参数 | 参数 | 类型 | 必填 | 默认值 | 描述 | |------|------|------|--------|------| | `q` | string | Y | - | 搜索查询(至少 2 个字符) | | `size` | integer | N | 5 | 返回结果数量(1-20) | #### 请求示例 ```bash curl "http://localhost:6002/search/instant?q=玩具&size=5" ``` #### 当前响应 ```json { "error": "/search/instant is not implemented yet. Use POST /search/ for production traffic.", "status_code": 501 } ``` ### 3.9 获取单个文档 - **端点**: `GET /search/{doc_id}` - **描述**: 根据文档 ID 获取单个商品详情,用于点击结果后的详情页或排查问题。 - **租户标识**:同 [3.1](#31-接口信息),通过请求头 `X-Tenant-ID` 或 query 参数 `tenant_id` 传递。 #### 路径参数 | 参数 | 类型 | 描述 | |------|------|------| | `doc_id` | string | 商品或文档 ID | #### 响应示例 ```json { "id": "12345", "source": { "title": { "zh": "芭比时尚娃娃" }, "min_price": 89.99, "category1_name": "玩具" } } ``` #### 请求示例 ```bash curl "http://localhost:6002/search/12345" -H "X-Tenant-ID: 162" # 或使用 query 参数:curl "http://localhost:6002/search/12345?tenant_id=162" ``` --- ## 响应格式说明 ### 4.1 标准响应结构 ```json { "results": [ { "spu_id": "12345", "title": "芭比时尚娃娃", "brief": "高品质芭比娃娃", "description": "详细描述...", "vendor": "美泰", "category": "玩具", "category_path": "玩具/娃娃/时尚", "category_name": "时尚", "category_id": "cat_001", "category_level": 3, "category1_name": "玩具", "category2_name": "娃娃", "category3_name": "时尚", "tags": ["娃娃", "玩具", "女孩"], "price": 89.99, "compare_at_price": 129.99, "currency": "USD", "image_url": "https://example.com/image.jpg", "in_stock": true, "sku_prices": [89.99, 99.99, 109.99], "sku_weights": [100, 150, 200], "sku_weight_units": ["g", "g", "g"], "total_inventory": 500, "option1_name": "color", "option2_name": "size", "option3_name": null, "specifications": [ {"sku_id": "sku_001", "name": "color", "value": "pink"}, {"sku_id": "sku_001", "name": "size", "value": "standard"} ], "skus": [ { "sku_id": "67890", "price": 89.99, "compare_at_price": 129.99, "sku": "BARBIE-001", "stock": 100, "weight": 0.1, "weight_unit": "kg", "option1_value": "pink", "option2_value": "standard", "option3_value": null, "image_src": "https://example.com/sku1.jpg" } ], "relevance_score": 8.5 } ], "total": 118, "max_score": 8.5, "facets": [ { "field": "category1_name", "label": "category1_name", "type": "terms", "values": [ { "value": "玩具", "label": "玩具", "count": 85, "selected": false } ] }, { "field": "specifications.color", "label": "color", "type": "terms", "values": [ { "value": "pink", "label": "pink", "count": 30, "selected": false } ] } ], "query_info": { "original_query": "芭比娃娃", "query_normalized": "芭比娃娃", "rewritten_query": "芭比娃娃", "detected_language": "zh", "translations": { "en": "barbie doll" }, "domain": "default" }, "suggestions": [], "related_searches": [], "took_ms": 45, "performance_info": null, "debug_info": null } ``` ### 4.2 响应字段说明 | 字段 | 类型 | 说明 | |------|------|------| | `results` | array | 搜索结果列表(SpuResult对象数组) | | `results[].spu_id` | string | SPU ID | | `results[].title` | string | 商品标题 | | `results[].price` | float | 价格(min_price) | | `results[].skus` | array | SKU列表(如果指定了`sku_filter_dimension`,则按维度过滤后的SKU) | | `results[].relevance_score` | float | 相关性分数 | | `total` | integer | 匹配的总文档数 | | `max_score` | float | 最高相关性分数 | | `facets` | array | 分面统计结果 | | `query_info` | object | query处理信息 | | `took_ms` | integer | 搜索耗时(毫秒) | #### 4.2.1 query_info 说明 `query_info` 包含本次搜索的查询解析与处理结果: | 子字段 | 类型 | 说明 | |--------|------|------| | `original_query` | string | 用户原始查询 | | `query_normalized` | string | 归一化后的查询(去空白、大小写等预处理,用于后续解析与改写) | | `rewritten_query` | string | 重写后的查询(同义词/词典扩展等) | | `detected_language` | string | 检测到的查询语言(如 `zh`、`en`) | | `translations` | object | 翻译结果,键为语言代码,值为翻译文本 | | `domain` | string | 查询域(如 `default`、`title`、`brand` 等) | ### 4.3 SpuResult字段说明 | 字段 | 类型 | 说明 | |------|------|------| | `spu_id` | string | SPU ID | | `title` | string | 商品标题(根据language参数自动选择 `title.zh` 或 `title.en`) | | `brief` | string | 商品短描述(根据language参数自动选择) | | `description` | string | 商品详细描述(根据language参数自动选择) | | `vendor` | string | 供应商/品牌(根据language参数自动选择) | | `category` | string | 类目(兼容字段,等同于category_name) | | `category_path` | string | 类目路径(多级,用于面包屑,根据language参数自动选择) | | `category_name` | string | 类目名称(展示用,根据language参数自动选择) | | `category_id` | string | 类目ID | | `category_level` | integer | 类目层级(1/2/3) | | `category1_name` | string | 一级类目名称 | | `category2_name` | string | 二级类目名称 | | `category3_name` | string | 三级类目名称 | | `tags` | array[string] | 标签列表 | | `price` | float | 价格(min_price) | | `compare_at_price` | float | 原价 | | `currency` | string | 货币单位(默认USD) | | `image_url` | string | 主图URL | | `in_stock` | boolean | 是否有库存(任意SKU有库存即为true) | | `sku_prices` | array[float] | 所有SKU价格列表 | | `sku_weights` | array[integer] | 所有SKU重量列表 | | `sku_weight_units` | array[string] | 所有SKU重量单位列表 | | `total_inventory` | integer | 总库存 | | `sales` | integer | 销量(展示销量) | | `option1_name` | string | 选项1名称(如"color") | | `option2_name` | string | 选项2名称(如"size") | | `option3_name` | string | 选项3名称 | | `specifications` | array[object] | 规格列表(与ES specifications字段对应) | | `skus` | array | SKU 列表 | | `relevance_score` | float | 相关性分数(默认为 ES 原始分数;当开启 AI 搜索时为融合后的最终分数) | ### 4.4 SkuResult字段说明 | 字段 | 类型 | 说明 | |------|------|------| | `sku_id` | string | SKU ID | | `price` | float | 价格 | | `compare_at_price` | float | 原价 | | `sku` | string | SKU编码(sku_code) | | `stock` | integer | 库存数量 | | `weight` | float | 重量 | | `weight_unit` | string | 重量单位 | | `option1_value` | string | 选项1取值(如color值) | | `option2_value` | string | 选项2取值(如size值) | | `option3_value` | string | 选项3取值 | | `image_src` | string | SKU图片地址 | ### 4.5 多语言字段说明 - `title`, `brief`, `description`, `vendor`, `category_path`, `category_name` 会根据请求的 `language` 参数自动选择对应的中英文字段 - `language="zh"`: 优先返回 `*_zh` 字段,如果为空则回退到 `*_en` 字段 - `language="en"`: 优先返回 `*_en` 字段,如果为空则回退到 `*_zh` 字段 --- ## 索引接口 本节内容与 `api/routes/indexer.py` 中的索引相关服务一致,包含以下接口: | 接口 | 方法 | 路径 | 说明 | |------|------|------|------| | 全量重建索引 | POST | `/indexer/reindex` | 将指定租户所有 SPU 导入 ES(不删现有索引) | | 增量索引 | POST | `/indexer/index` | 按 SPU ID 列表索引/删除,支持自动检测删除与显式删除 | | 查询文档 | POST | `/indexer/documents` | 按 SPU ID 列表查询 ES 文档,不写入 ES | | 构建 ES 文档(正式) | POST | `/indexer/build-docs` | 由上游提供 MySQL 行数据,返回 ES-ready 文档,不写 ES | | 构建 ES 文档(测试) | POST | `/indexer/build-docs-from-db` | 由本服务查库并构建文档,仅测试/调试用 | | 索引健康检查 | GET | `/indexer/health` | 检查索引服务与数据库连接状态 | ### 5.0 为租户创建索引 为租户创建索引需要两个步骤: 1. **创建索引结构**(可选,仅在需要更新 mapping 或在新环境首次创建时执行) - 使用脚本创建 ES 索引结构(基于 `mappings/search_products.json`) - 如果索引已存在,会提示用户确认(会删除现有数据) 2. **导入数据**(必需) - 使用全量索引接口 `/indexer/reindex` 导入数据 **创建索引结构(支持多环境 namespace)**: ```bash # 以 UAT 环境为例: # 1. 准备 UAT 环境的 .env(包含 UAT 的 ES_HOST/DB_HOST 等) # 2. 设置环境前缀(也可以直接在 .env 中配置): export RUNTIME_ENV=uat export ES_INDEX_NAMESPACE=uat_ # 3. 为 tenant_id=170 创建索引结构 ./scripts/create_tenant_index.sh 170 ``` 脚本会自动从项目根目录的 `.env` 文件加载 ES 配置,并根据 `ES_INDEX_NAMESPACE` 创建: - prod 环境(ES_INDEX_NAMESPACE 为空):`search_products_tenant_170` - UAT 环境(ES_INDEX_NAMESPACE=uat_):`uat_search_products_tenant_170` **注意事项**: - ⚠️ 如果索引已存在,脚本会提示确认,确认后会删除现有数据 - 创建索引后,**必须**调用 `/indexer/reindex` 导入数据 - 如果只是更新数据而不需要修改索引结构,直接使用 `/indexer/reindex` 即可 --- ### 5.1 全量索引接口 - **端点**: `POST /indexer/reindex` - **描述**: 全量索引,将指定租户的所有SPU数据导入到ES索引(不会删除现有索引)。**推荐仅用于自测/运维场景**;生产环境下更推荐由 Java 等上游控制调度与写 ES。 #### 请求参数 ```json { "tenant_id": "162", "batch_size": 500 } ``` | 参数 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | `tenant_id` | string | Y | - | 租户ID | | `batch_size` | integer | N | 500 | 批量导入大小 | #### 响应格式 **成功响应(200 OK)**(示例,实际 `index_name` 会带上 tenant 和环境前缀): ```json { "success": true, "total": 1000, "indexed": 1000, "failed": 0, "elapsed_time": 12.34, "index_name": "search_products_tenant_162", "tenant_id": "162" } ``` **错误响应**: - `400 Bad Request`: 参数错误 - `503 Service Unavailable`: 服务未初始化 #### 请求示例 **全量索引(不会删除现有索引)**: ```bash curl -X POST "http://localhost:6004/indexer/reindex" \ -H "Content-Type: application/json" \ -d '{ "tenant_id": "162", "batch_size": 500 }' ``` **查看日志**: ```bash # 查看API日志(包含索引操作日志) tail -f logs/api.log # 或者查看所有日志文件 tail -f logs/*.log ``` > ⚠️ **重要提示**:如需 **创建索引结构**,请参考 [5.0 为租户创建索引](#50-为租户创建索引) 章节,使用 `./scripts/create_tenant_index.sh `。创建后需要调用 `/indexer/reindex` 导入数据。 **查看索引日志**: 索引操作的所有关键信息都会记录到 `logs/indexer.log` 文件中(JSON 格式),包括: - 请求开始和结束时间 - 租户ID、SPU ID、操作类型 - 每个SPU的处理状态 - ES批量写入结果 - 成功/失败统计和详细错误信息 ```bash # 实时查看索引日志(包含全量和增量索引的所有操作) tail -f logs/indexer.log # 使用 grep 查询(简单方式) # 查看全量索引日志 grep "\"index_type\":\"bulk\"" logs/indexer.log | tail -100 # 查看增量索引日志 grep "\"index_type\":\"incremental\"" logs/indexer.log | tail -100 # 查看特定租户的索引日志 grep "\"tenant_id\":\"162\"" logs/indexer.log | tail -100 # 使用 jq 查询(推荐,更精确的 JSON 查询) # 安装 jq: sudo apt-get install jq 或 brew install jq # 查看全量索引日志 cat logs/indexer.log | jq 'select(.index_type == "bulk")' | tail -100 # 查看增量索引日志 cat logs/indexer.log | jq 'select(.index_type == "incremental")' | tail -100 # 查看特定租户的索引日志 cat logs/indexer.log | jq 'select(.tenant_id == "162")' | tail -100 # 查看失败的索引操作 cat logs/indexer.log | jq 'select(.operation == "request_complete" and .failed_count > 0)' # 查看特定SPU的处理日志 cat logs/indexer.log | jq 'select(.spu_id == "123")' # 查看最近的索引请求统计 cat logs/indexer.log | jq 'select(.operation == "request_complete") | {timestamp, index_type, tenant_id, total_count, success_count, failed_count, elapsed_time}' ``` ### 5.2 增量索引接口 - **端点**: `POST /indexer/index` - **描述**: 增量索引接口,根据指定的SPU ID列表进行索引,直接将数据写入ES。用于增量更新指定商品。**推荐仅作为内部/调试入口**;正式对接建议改用 `/indexer/build-docs`,由上游写 ES。 **删除说明**: - `spu_ids`中的SPU:如果数据库`deleted=1`,自动从ES删除,响应状态为`deleted` - `delete_spu_ids`中的SPU:直接删除,响应状态为`deleted`、`not_found`或`failed` #### 请求参数 ```json { "tenant_id": "162", "spu_ids": ["123", "456", "789"], "delete_spu_ids": ["100", "101"] } ``` | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `tenant_id` | string | Y | 租户ID | | `spu_ids` | array[string] | N | SPU ID列表(1-100个),要索引的SPU。如果为空,则只执行删除操作 | | `delete_spu_ids` | array[string] | N | 显式指定要删除的SPU ID列表(1-100个),可选。无论数据库状态如何,都会从ES中删除这些SPU | **注意**: - `spu_ids` 和 `delete_spu_ids` 不能同时为空 - 每个列表最多支持100个SPU ID - 如果SPU在`spu_ids`中且数据库`deleted=1`,会自动从ES删除(自动检测删除) #### 响应格式 ```json { "spu_ids": [ { "spu_id": "123", "status": "indexed" }, { "spu_id": "456", "status": "deleted" }, { "spu_id": "789", "status": "failed", "msg": "SPU not found (unexpected)" } ], "delete_spu_ids": [ { "spu_id": "100", "status": "deleted" }, { "spu_id": "101", "status": "not_found" }, { "spu_id": "102", "status": "failed", "msg": "Failed to delete from ES: Connection timeout" } ], "total": 6, "success_count": 4, "failed_count": 2, "elapsed_time": 1.23, "index_name": "search_products", "tenant_id": "162" } ``` | 字段 | 类型 | 说明 | |------|------|------| | `spu_ids` | array | spu_ids对应的响应列表,每个元素包含 `spu_id` 和 `status` | | `spu_ids[].status` | string | 状态:`indexed`(已索引)、`deleted`(已删除,自动检测)、`failed`(失败) | | `spu_ids[].msg` | string | 当status为`failed`时,包含失败原因(可选) | | `delete_spu_ids` | array | delete_spu_ids对应的响应列表,每个元素包含 `spu_id` 和 `status` | | `delete_spu_ids[].status` | string | 状态:`deleted`(已删除)、`not_found`(ES中不存在)、`failed`(失败) | | `delete_spu_ids[].msg` | string | 当status为`failed`时,包含失败原因(可选) | | `total` | integer | 总处理数量(spu_ids数量 + delete_spu_ids数量) | | `success_count` | integer | 成功数量(indexed + deleted + not_found) | | `failed_count` | integer | 失败数量 | | `elapsed_time` | float | 耗时(秒) | | `index_name` | string | 索引名称 | | `tenant_id` | string | 租户ID | **状态说明**: - `spu_ids` 的状态: - `indexed`: SPU已成功索引到ES - `deleted`: SPU在数据库中被标记为deleted=1,已从ES删除(自动检测) - `failed`: 处理失败,会包含`msg`字段说明失败原因 - `delete_spu_ids` 的状态: - `deleted`: SPU已从ES成功删除 - `not_found`: SPU在ES中不存在(也算成功,可能已经被删除过) - `failed`: 删除失败,会包含`msg`字段说明失败原因 #### 请求示例 **示例1:普通增量索引(自动检测删除)**: ```bash curl -X POST "http://localhost:6004/indexer/index" \ -H "Content-Type: application/json" \ -d '{ "tenant_id": "162", "spu_ids": ["123", "456", "789"] }' ``` 说明:如果SPU 456在数据库中`deleted=1`,会自动从ES删除,在响应中`spu_ids`列表里456的状态为`deleted`。 **示例2:显式删除(批量删除)**: ```bash curl -X POST "http://localhost:6004/indexer/index" \ -H "Content-Type: application/json" \ -d '{ "tenant_id": "162", "spu_ids": ["123", "456"], "delete_spu_ids": ["100", "101", "102"] }' ``` 说明:SPU 100、101、102会被显式删除,无论数据库状态如何。 **示例3:仅删除(不索引)**: ```bash curl -X POST "http://localhost:6004/indexer/index" \ -H "Content-Type: application/json" \ -d '{ "tenant_id": "162", "spu_ids": [], "delete_spu_ids": ["100", "101"] }' ``` 说明:只执行删除操作,不进行索引。 **示例4:混合操作(索引+删除)**: ```bash curl -X POST "http://localhost:6004/indexer/index" \ -H "Content-Type: application/json" \ -d '{ "tenant_id": "162", "spu_ids": ["123", "456", "789"], "delete_spu_ids": ["100", "101"] }' ``` 说明:同时执行索引和删除操作。 #### 日志说明 增量索引操作的所有关键信息都会记录到 `logs/indexer.log` 文件中(JSON格式),包括: - 请求开始和结束时间 - 每个SPU的处理状态(获取、转换、索引、删除) - ES批量写入结果 - 成功/失败统计 - 详细的错误信息 日志查询方式请参考[5.1节查看索引日志](#51-全量重建索引接口)部分。 ### 5.3 查询文档接口 - **端点**: `POST /indexer/documents` - **描述**: 查询文档接口,根据SPU ID列表获取ES文档数据(**不写入ES**)。用于查看、调试或验证SPU数据。 #### 请求参数 ```json { "tenant_id": "162", "spu_ids": ["123", "456", "789"] } ``` | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `tenant_id` | string | Y | 租户ID | | `spu_ids` | array[string] | Y | SPU ID列表(1-100个) | #### 响应格式 ```json { "success": [ { "spu_id": "123", "document": { "tenant_id": "162", "spu_id": "123", "title": { "zh": "商品标题" }, ... } }, { "spu_id": "456", "document": {...} } ], "failed": [ { "spu_id": "789", "error": "SPU not found or deleted" } ], "total": 3, "success_count": 2, "failed_count": 1 } ``` | 字段 | 类型 | 说明 | |------|------|------| | `success` | array | 成功获取的SPU列表,每个元素包含 `spu_id` 和 `document`(完整的ES文档数据) | | `failed` | array | 失败的SPU列表,每个元素包含 `spu_id` 和 `error`(失败原因) | | `total` | integer | 总SPU数量 | | `success_count` | integer | 成功数量 | | `failed_count` | integer | 失败数量 | #### 请求示例 **单个SPU查询**: ```bash curl -X POST "http://localhost:6004/indexer/documents" \ -H "Content-Type: application/json" \ -d '{ "tenant_id": "162", "spu_ids": ["123"] }' ``` **批量SPU查询**: ```bash curl -X POST "http://localhost:6004/indexer/documents" \ -H "Content-Type: application/json" \ -d '{ "tenant_id": "162", "spu_ids": ["123", "456", "789"] }' ``` #### 与 `/indexer/index` 的区别 | 接口 | 功能 | 是否写入ES | 返回内容 | |------|------|-----------|----------| | `/indexer/documents` | 查询SPU文档数据 | 否 | 返回完整的ES文档数据 | | `/indexer/index` | 增量索引 | 是 | 返回成功/失败列表和统计信息 | **使用场景**: - `/indexer/documents`:用于查看、调试或验证SPU数据,不修改ES索引 - `/indexer/index`:用于实际的增量索引操作,将更新的SPU数据同步到ES ### 5.4 索引健康检查接口 - **端点**: `GET /indexer/health` - **描述**: 检查索引服务健康状态(与 `api/routes/indexer.py` 中 `indexer_health_check` 一致) #### 响应格式 ```json { "status": "available", "database": "connected", "preloaded_data": { "category_mappings": 150 } } ``` | 字段 | 类型 | 说明 | |------|------|------| | `status` | string | `available`(服务可用)、`unavailable`(未初始化)、`error`(异常) | | `database` | string | 数据库连接状态,如 `connected` 或 `disconnected: ...` | | `preloaded_data.category_mappings` | integer | 已加载的分类映射数量 | #### 请求示例 ```bash curl -X GET "http://localhost:6004/indexer/health" ``` ### 5.5 文档构建接口(正式对接推荐) #### 5.5.1 `POST /indexer/build-docs` - **描述**: 基于调用方(通常是 Java 索引程序)提供的 **MySQL 行数据** 构建 ES 文档(doc),**不写入 ES**。 由本服务负责“如何构建 doc”(多语言、翻译、向量、规格聚合等),由调用方负责“何时调度 + 如何写 ES”。 #### 请求参数 ```json { "tenant_id": "170", "items": [ { "spu": { "id": 223167, "tenant_id": 170, "title": "..." }, "skus": [ { "id": 3988393, "spu_id": 223167, "price": 25.99, "compare_at_price": 25.99 } ], "options": [] } ] } ``` | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `tenant_id` | string | Y | 租户 ID | | `items` | array | Y | 需构建 doc 的 SPU 列表(每项含 `spu`、`skus`、`options`),**单次最多 200 条** | > `spu` / `skus` / `options` 字段应当直接使用从 `shoplazza_product_spu` / `shoplazza_product_sku` / `shoplazza_product_option` 查询出的行字段。 #### 请求示例(完整 curl) > 完整请求体参考 `scripts/test_build_docs_api.py` 中的 `build_sample_request()`。 ```bash # 单条 SPU 示例(含 spu、skus、options) curl -X POST "http://localhost:6004/indexer/build-docs" \ -H "Content-Type: application/json" \ -d '{ "tenant_id": "162", "items": [ { "spu": { "id": 10001, "tenant_id": "162", "title": "测试T恤 纯棉短袖", "brief": "舒适纯棉,多色可选", "description": "这是一款适合日常穿着的纯棉T恤,透气吸汗。", "vendor": "测试品牌", "category": "服装/上衣/T恤", "category_id": 100, "category_level": 2, "category_path": "服装/上衣/T恤", "fake_sales": 1280, "image_src": "https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg", "tags": "T恤,纯棉,短袖,夏季", "create_time": "2024-01-01T00:00:00Z", "update_time": "2024-01-01T00:00:00Z" }, "skus": [ { "id": 20001, "spu_id": 10001, "price": 99.0, "compare_at_price": 129.0, "sku": "SKU-TSHIRT-001", "inventory_quantity": 50, "option1": "黑色", "option2": "M", "option3": null }, { "id": 20002, "spu_id": 10001, "price": 99.0, "compare_at_price": 129.0, "sku": "SKU-TSHIRT-002", "inventory_quantity": 30, "option1": "白色", "option2": "L", "option3": null } ], "options": [ {"id": 1, "position": 1, "name": "颜色"}, {"id": 2, "position": 2, "name": "尺码"} ] } ] }' ``` 生产环境替换 `localhost:6004` 为实际 Indexer 地址,如 `http://43.166.252.75:6004`。 #### 响应示例(节选) ```json { "tenant_id": "170", "docs": [ { "tenant_id": "170", "spu_id": "223167", "title": { "en": "...", "zh": "..." }, "tags": ["Floerns", "Clothing", "Shoes & Jewelry"], "skus": [ { "sku_id": "3988393", "price": 25.99, "compare_at_price": 25.99, "stock": 100 } ], "min_price": 25.99, "max_price": 25.99, "compare_at_price": 25.99, "total_inventory": 100, "title_embedding": [/* 1024 维向量 */] // 其余字段与 mappings/search_products.json 一致 } ], "total": 1, "success_count": 1, "failed_count": 0, "failed": [] } ``` | 字段 | 类型 | 说明 | |------|------|------| | `tenant_id` | string | 租户 ID | | `docs` | array | 构建成功的 ES 文档列表,与 `mappings/search_products.json` 一致 | | `total` | integer | 请求的 items 总数 | | `success_count` | integer | 成功构建数量 | | `failed_count` | integer | 失败数量 | | `failed` | array | 失败项列表,每项含 `spu_id`、`error` | #### 使用建议 - **生产环境推荐流程**: 1. Java 根据业务逻辑决定哪些 SPU 需要(全量/增量)处理; 2. Java 从 MySQL 查询 SPU/SKU/Option 行,拼成 `items`; 3. 调用 `/indexer/build-docs` 获取 ES-ready `docs`; 4. Java 使用自己的 ES 客户端写入 `search_products_tenant_{tenant_id}`。 ### 5.6 文档构建接口(测试 / 自测) #### 5.6.1 `POST /indexer/build-docs-from-db` - **描述**: 仅用于测试/调试:调用方只提供 `tenant_id` 和 `spu_ids`,由 indexer 服务内部从 MySQL 查询 SPU/SKU/Option,然后调用与 `/indexer/build-docs` 相同的文档构建逻辑,返回 ES-ready doc。**生产环境请使用 `/indexer/build-docs`,由上游查库并写 ES。** #### 请求参数 ```json { "tenant_id": "170", "spu_ids": ["223167", "223168"] } ``` | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `tenant_id` | string | Y | 租户 ID | | `spu_ids` | array[string] | Y | SPU ID 列表,**单次最多 200 个** | #### 响应格式 与 `/indexer/build-docs` 相同:`tenant_id`、`docs`、`total`、`success_count`、`failed_count`、`failed`。 #### 请求示例 ```bash curl -X POST "http://127.0.0.1:6004/indexer/build-docs-from-db" \ -H "Content-Type: application/json" \ -d '{"tenant_id": "170", "spu_ids": ["223167"]}' ``` 返回结构与 `/indexer/build-docs` 相同,可直接用于对比 ES 实际文档或调试字段映射问题。 --- ## 管理接口 ### 6.1 健康检查 - **端点**: `GET /admin/health` - **描述**: 检查服务与依赖(如 Elasticsearch)状态。 ```json { "status": "healthy", "elasticsearch": "connected", "tenant_id": "tenant1" } ``` ### 6.2 获取配置 - **端点**: `GET /admin/config` - **描述**: 返回当前租户的脱敏配置,便于核对索引及排序表达式。 ```json { "tenant_id": "tenant1", "tenant_name": "Tenant1 Test Instance", "es_index_name": "search_tenant1", "num_fields": 20, "num_indexes": 4, "supported_languages": ["zh", "en", "ru"], "ranking_expression": "bm25() + 0.2*text_embedding_relevance()", "spu_enabled": false } ``` ### 6.3 索引统计 - **端点**: `GET /admin/stats` - **描述**: 获取指定租户索引文档数量与磁盘大小,方便监控。 - **租户标识**:通过请求头 `X-Tenant-ID` 或 query 参数 `tenant_id` 传递(必填)。 ```json { "tenant_id": "162", "index_name": "search_products_tenant_162", "document_count": 10000, "size_mb": 523.45 } ``` --- ## 7. 微服务接口(向量、重排、翻译) 以下三个微服务独立部署,**外部系统可直接调用**。它们被搜索后端(6002)和索引服务(6004)内部使用,也可供其他业务系统直接对接。 | 服务 | 默认端口 | Base URL | 说明 | |------|----------|----------|------| | 向量服务 | 6005 | `http://localhost:6005` | 文本/图片向量化,用于语义搜索与以图搜图 | | 翻译服务 | 6006 | `http://localhost:6006` | 多语言翻译(Qwen/DeepL) | | 重排服务 | 6007 | `http://localhost:6007` | 对检索结果进行二次排序 | 生产环境请将 `localhost` 替换为实际服务地址。 ### 7.1 向量服务(Embedding) - **Base URL**: `http://localhost:6005`(可通过 `EMBEDDING_SERVICE_URL` 覆盖) - **启动**: `./scripts/start_embedding_service.sh` - **依赖**: - 文本向量后端默认走 TEI(`http://127.0.0.1:8080`) - 图片向量依赖 `cnclip`(`grpc://127.0.0.1:51000`) - TEI 默认使用 GPU(`TEI_USE_GPU=1`);当配置为 GPU 且不可用时会启动失败(不会自动降级到 CPU) - cnclip 默认使用 `cuda`;若配置为 `cuda` 但 GPU 不可用会启动失败(不会自动降级到 `cpu`) #### 7.1.1 `POST /embed/text` — 文本向量化 将文本列表转为 1024 维向量,用于语义搜索、文档索引等。 **请求体**(JSON 数组): ```json ["文本1", "文本2", "文本3"] ``` **响应**(JSON 数组,与输入一一对应): ```json [[0.01, -0.02, ...], [0.03, 0.01, ...], ...] ``` **完整 curl 示例**: ```bash curl -X POST "http://localhost:6005/embed/text" \ -H "Content-Type: application/json" \ -d '["芭比娃娃 儿童玩具", "纯棉T恤 短袖"]' ``` #### 7.1.2 `POST /embed/image` — 图片向量化 将图片 URL 或路径转为向量,用于以图搜图。 前置条件:`cnclip` 服务已启动(默认端口 `51000`)。若未启动,`/embed/image` 会返回 500。 **请求体**(JSON 数组): ```json ["https://example.com/image1.jpg", "https://example.com/image2.jpg"] ``` **响应**(JSON 数组,与输入一一对应): ```json [[0.01, -0.02, ...], [0.03, 0.01, ...], ...] ``` **完整 curl 示例**: ```bash curl -X POST "http://localhost:6005/embed/image" \ -H "Content-Type: application/json" \ -d '["https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg"]' ``` #### 7.1.3 `GET /health` — 健康检查 ```bash curl "http://localhost:6005/health" ``` ### 7.2 重排服务(Reranker) - **Base URL**: `http://localhost:6007`(可通过 `RERANKER_SERVICE_URL` 覆盖) - **启动**: `./scripts/start_reranker.sh` 说明:默认后端为 `qwen3_vllm`(`Qwen/Qwen3-Reranker-0.6B`),需要可用 GPU 显存。 #### 7.2.1 `POST /rerank` — 结果重排 根据 query 与 doc 的相关性对文档列表重新打分排序。 **请求体**: ```json { "query": "玩具 芭比", "docs": [ "12PCS 6 Types of Dolls with Bottles", "纯棉T恤 短袖 夏季" ], "normalize": true } ``` | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `query` | string | Y | 搜索查询 | | `docs` | array[string] | Y | 待重排的文档列表(单次最多由服务端配置限制) | | `normalize` | boolean | N | 是否对分数做 sigmoid 归一化,默认 true | **响应**: ```json { "scores": [0.92, 0.15], "meta": { "service_elapsed_ms": 45.2, "input_docs": 2, "unique_docs": 2 } } ``` **完整 curl 示例**: ```bash curl -X POST "http://localhost:6007/rerank" \ -H "Content-Type: application/json" \ -d '{ "query": "玩具 芭比", "docs": ["12PCS 6 Types of Dolls with Bottles", "纯棉T恤 短袖"], "normalize": true }' ``` #### 7.2.2 `GET /health` — 健康检查 ```bash curl "http://localhost:6007/health" ``` ### 7.3 翻译服务(Translation) - **Base URL**: `http://localhost:6006`(可通过 `TRANSLATION_SERVICE_URL` 覆盖) - **启动**: `./scripts/start_translator.sh` #### 7.3.1 `POST /translate` — 文本翻译 支持 Qwen(默认)与 DeepL 模型,适用于商品名称、描述等电商场景。 **请求体**: ```json { "text": "商品名称", "target_lang": "en", "source_lang": "zh", "model": "qwen" } ``` | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `text` | string | Y | 待翻译文本 | | `target_lang` | string | Y | 目标语言:`zh`、`en`、`ru` 等 | | `source_lang` | string | N | 源语言,不传则自动检测 | | `model` | string | N | `qwen`(默认)或 `deepl` | **响应**: ```json { "text": "商品名称", "target_lang": "en", "source_lang": "zh", "translated_text": "Product name", "status": "success", "model": "qwen" } ``` **完整 curl 示例**: 中文 → 英文: ```bash curl -X POST "http://localhost:6006/translate" \ -H "Content-Type: application/json" \ -d '{ "text": "商品名称", "target_lang": "en", "source_lang": "zh" }' ``` 俄文 → 英文: ```bash curl -X POST "http://localhost:6006/translate" \ -H "Content-Type: application/json" \ -d '{ "text": "Название товара", "target_lang": "en", "source_lang": "ru" }' ``` 使用 DeepL 模型: ```bash curl -X POST "http://localhost:6006/translate" \ -H "Content-Type: application/json" \ -d '{ "text": "商品名称", "target_lang": "en", "source_lang": "zh", "model": "deepl" }' ``` #### 7.3.2 `GET /health` — 健康检查 ```bash curl "http://localhost:6006/health" ``` --- ## 8. 常见场景示例 以下示例仅展示**请求体**(body);实际调用时请加上请求头 `X-Tenant-ID: <租户ID>`(或 URL 参数 `tenant_id`),参见 [3.1 接口信息](#31-接口信息)。 ### 8.1 基础搜索与排序 **按价格从低到高排序**: ```json { "query": "玩具", "size": 20, "from": 0, "sort_by": "price", "sort_order": "asc" } ``` **按价格从高到低排序**: ```json { "query": "玩具", "size": 20, "from": 0, "sort_by": "price", "sort_order": "desc" } ``` **按销量从高到低排序**: ```json { "query": "玩具", "size": 20, "from": 0, "sort_by": "sales", "sort_order": "desc" } ``` **按默认(相关性)排序**: ```json { "query": "玩具", "size": 20, "from": 0 } ``` ### 8.2 过滤搜索 **需求**: 搜索"玩具",筛选类目为"益智玩具",价格在50-200之间 ```json { "query": "玩具", "size": 20, "language": "zh", "filters": { "category_name": "益智玩具" }, "range_filters": { "min_price": { "gte": 50, "lte": 200 } } } ``` **需求**: 搜索"手机",筛选多个品牌,价格范围 ```json { "query": "手机", "size": 20, "language": "zh", "filters": { "vendor.zh.keyword": ["品牌A", "品牌B"] }, "range_filters": { "min_price": { "gte": 50, "lte": 200 } } } ``` ### 8.3 分面搜索 **需求**: 搜索"玩具",获取类目和规格的分面统计,用于构建筛选器 ```json { "query": "玩具", "size": 20, "language": "zh", "facets": [ {"field": "category1_name", "size": 15, "type": "terms"}, {"field": "category2_name", "size": 10, "type": "terms"}, {"field": "specifications", "size": 10, "type": "terms"} ] } ``` **需求**: 搜索"手机",获取价格区间和规格的分面统计 ```json { "query": "手机", "size": 20, "language": "zh", "facets": [ { "field": "min_price", "type": "range", "ranges": [ {"key": "0-50", "to": 50}, {"key": "50-100", "from": 50, "to": 100}, {"key": "100-200", "from": 100, "to": 200}, {"key": "200+", "from": 200} ] }, { "field": "specifications", "size": 10, "type": "terms" } ] } ``` ### 8.4 规格过滤与分面 **需求**: 搜索"手机",筛选color为"white"的商品 ```json { "query": "手机", "size": 20, "language": "zh", "filters": { "specifications": { "name": "color", "value": "white" } } } ``` **需求**: 搜索"手机",筛选color为"white"且size为"256GB"的商品 ```json { "query": "手机", "size": 20, "language": "zh", "filters": { "specifications": [ {"name": "color", "value": "white"}, {"name": "size", "value": "256GB"} ] } } ``` **需求**: 搜索"手机",筛选size为"3"、"4"或"5",且color为"green"的商品 ```json { "query": "手机", "size": 20, "language": "zh", "filters": { "specifications": [ {"name": "size", "value": "3"}, {"name": "size", "value": "4"}, {"name": "size", "value": "5"}, {"name": "color", "value": "green"} ] } } ``` **需求**: 搜索"手机",获取所有规格的分面统计 ```json { "query": "手机", "size": 20, "language": "zh", "facets": [ {"field": "specifications", "size": 10, "type": "terms"} ] } ``` **需求**: 只获取"color"和"size"规格的分面统计 ```json { "query": "手机", "size": 20, "language": "zh", "facets": [ {"field": "specifications.color", "size": 20, "type": "terms"}, {"field": "specifications.size", "size": 15, "type": "terms"} ] } ``` **需求**: 搜索"手机",筛选类目和规格,并获取对应的分面统计 ```json { "query": "手机", "size": 20, "language": "zh", "filters": { "category_name": "手机", "specifications": { "name": "color", "value": "white" } }, "facets": [ {"field": "category1_name", "size": 15, "type": "terms"}, {"field": "category2_name", "size": 10, "type": "terms"}, {"field": "specifications.color", "size": 20, "type": "terms"}, {"field": "specifications.size", "size": 15, "type": "terms"} ] } ``` ### 8.5 SKU筛选 **需求**: 搜索"芭比娃娃",每个SPU下按颜色筛选,每种颜色只显示一个SKU ```json { "query": "芭比娃娃", "size": 20, "sku_filter_dimension": ["color"] } ``` **说明**: - 如果 `option1_name` 为 `"color"`,则使用 `sku_filter_dimension: ["color"]` 可以按颜色分组 - 每个SPU下,每种颜色只会返回第一个SKU - 如果维度不匹配,返回所有SKU(不进行过滤) ### 8.7 分页查询 **需求**: 获取第2页结果(每页20条) ```json { "query": "手机", "size": 20, "from": 20 } ``` --- ## 9. 数据模型 ### 9.1 商品字段定义 | 字段名 | 类型 | 描述 | |--------|------|------| | `tenant_id` | keyword | 租户ID(多租户隔离) | | `spu_id` | keyword | SPU ID | | `title.` | object/text | 商品标题(多语言对象,如 `title.zh`, `title.en`) | | `brief.` | object/text | 商品短描述(多语言对象,如 `brief.zh`, `brief.en`) | | `description.` | object/text | 商品详细描述(多语言对象,如 `description.zh`, `description.en`) | | `vendor.` | object/text | 供应商/品牌(多语言对象,且带 keyword 子字段,如 `vendor.zh.keyword`) | | `category_path.` | object/text | 类目路径(多语言对象,用于搜索,如 `category_path.zh`) | | `category_name_text.` | object/text | 类目名称(多语言对象,用于搜索,如 `category_name_text.zh`) | | `category_id` | keyword | 类目ID | | `category_name` | keyword | 类目名称(用于过滤) | | `category_level` | integer | 类目层级 | | `category1_name`, `category2_name`, `category3_name` | keyword | 多级类目名称(用于过滤和分面) | | `tags` | keyword | 标签(数组) | | `specifications` | nested | 规格(嵌套对象数组) | | `option1_name`, `option2_name`, `option3_name` | keyword | 选项名称 | | `min_price`, `max_price` | float | 最低/最高价格 | | `compare_at_price` | float | 原价 | | `sku_prices` | float | SKU价格列表(数组) | | `sku_weights` | long | SKU重量列表(数组) | | `sku_weight_units` | keyword | SKU重量单位列表(数组) | | `total_inventory` | long | 总库存 | | `sales` | long | 销量(展示销量) | | `skus` | nested | SKU详细信息(嵌套对象数组) | | `create_time`, `update_time` | date | 创建/更新时间 | | `title_embedding` | dense_vector | 标题向量(1024维,仅用于搜索) | | `image_embedding` | nested | 图片向量(嵌套,仅用于搜索) | > 所有租户共享统一的索引结构。文本字段支持中英文双语,后端根据 `language` 参数自动选择对应字段返回。 ### 9.2 字段类型速查 | 类型 | ES Mapping | 用途 | |------|------------|------| | `text` | `text` | 全文检索(支持中英文分析器) | | `keyword` | `keyword` | 精确匹配、聚合、排序 | | `integer` | `integer` | 整数 | | `long` | `long` | 长整数 | | `float` | `float` | 浮点数 | | `date` | `date` | 日期时间 | | `nested` | `nested` | 嵌套对象(specifications, skus, image_embedding) | | `dense_vector` | `dense_vector` | 向量字段(title_embedding,仅用于搜索) | ### 9.3 常用字段列表 #### 过滤字段 - `category_name`: 类目名称 - `category1_name`, `category2_name`, `category3_name`: 多级类目 - `category_id`: 类目ID - `vendor.zh.keyword`, `vendor.en.keyword`: 供应商/品牌(使用keyword子字段) - `tags`: 标签(keyword类型) - `option1_name`, `option2_name`, `option3_name`: 选项名称 - `specifications`: 规格过滤(嵌套字段,格式见[过滤器详解](#33-过滤器详解)) #### 范围字段 - `min_price`: 最低价格 - `max_price`: 最高价格 - `compare_at_price`: 原价 - `create_time`: 创建时间 - `update_time`: 更新时间 #### 排序字段 - `price`: 价格(后端自动根据sort_order映射:asc→min_price,desc→max_price) - `sales`: 销量 - `create_time`: 创建时间 - `update_time`: 更新时间 - `relevance_score`: 相关性分数(默认,不指定sort_by时使用) **注意**: 前端只需传 `price`,后端会自动处理: - `sort_by: "price"` + `sort_order: "asc"` → 按 `min_price` 升序(价格从低到高) - `sort_by: "price"` + `sort_order: "desc"` → 按 `max_price` 降序(价格从高到低) ### 9.4 支持的分析器 | 分析器 | 语言 | 描述 | |--------|------|------| | `index_ansj` | 中文 | 中文索引分析器(用于中文字段) | | `query_ansj` | 中文 | 中文查询分析器(用于中文字段) | | `hanlp_index` ⚠️ TODO(暂不支持) | 中文 | 中文索引分析器(用于中文字段) | | `hanlp_standard` ⚠️ TODO(暂不支持) | 中文 | 中文查询分析器(用于中文字段) | | `english` | 英文 | 标准英文分析器(用于英文字段) | | `lowercase` | - | 小写标准化器(用于keyword子字段) |