diff --git a/API_DOCUMENTATION.md b/API_DOCUMENTATION.md
deleted file mode 100644
index c3115ec..0000000
--- a/API_DOCUMENTATION.md
+++ /dev/null
@@ -1,938 +0,0 @@
-# 搜索引擎 API 接口文档
-
-## 概述
-
-本文档描述了电商搜索 SaaS 系统的 RESTful API 接口。系统提供强大的搜索功能,包括:
-
-- **多语言搜索**:支持中文、英文、俄文等多语言查询和自动翻译
-- **语义搜索**:基于 BGE-M3 文本向量和 CN-CLIP 图片向量的语义检索
-- **布尔表达式**:支持 AND、OR、RANK、ANDNOT 操作符
-- **灵活过滤**:精确匹配过滤器和数值范围过滤器
-- **分面搜索**:动态生成过滤选项,提供分组统计
-- **自定义排序**:支持按任意字段排序
-- **个性化排序**:可配置的相关性排序表达式
-
-## 基础信息
-
-- **Base URL**: `http://your-domain:6002` (http://120.76.41.98:6002)
-- **协议**: HTTP/HTTPS
-- **数据格式**: JSON
-- **字符编码**: UTF-8
-
-## 搜索接口
-
-### 1. 文本搜索
-
-**端点**: `POST /search/`
-
-**描述**: 执行文本搜索查询,支持多语言、布尔表达式、过滤器和分面搜索。
-
-#### 请求参数
-
-```json
-{
- "query": "string (required)",
- "size": 10,
- "from": 0,
- "filters": {},
- "range_filters": {},
- "facets": [],
- "sort_by": "string",
- "sort_order": "desc",
- "min_score": 0.0,
- "debug": false,
- "user_id": "string",
- "session_id": "string"
-}
-```
-
-#### 参数说明
-
-| 参数 | 类型 | 必填 | 默认值 | 描述 |
-|------|------|------|--------|------|
-| `query` | string | Y | - | 搜索查询字符串,支持布尔表达式(AND, OR, RANK, ANDNOT) |
-| `size` | integer | N | 10 | 返回结果数量(1-100) |
-| `from` | integer | N | 0 | 分页偏移量 |
-| `filters` | object | N | null | 精确匹配过滤器(见下文) |
-| `range_filters` | object | N | null | 数值范围过滤器(见下文) |
-| `facets` | array | N | null | 分面配置(见下文) |
-| `sort_by` | string | N | null | 排序字段名 |
-| `sort_order` | string | N | "desc" | 排序方向:`asc` 或 `desc` |
-| `min_score` | float | N | null | 最小相关性分数阈值 |
-| `debug` | boolean | N | false | 是否返回调试信息 |
-| `user_id` | string | N | null | 用户ID(用于个性化,预留) |
-| `session_id` | string | N | null | 会话ID(用于分析,预留) |
-
-#### 过滤器详解
-
-##### 精确匹配过滤器 (filters)
-
-用于精确匹配或多值匹配(OR 逻辑)。
-
-**格式**:
-```json
-{
- "filters": {
- "categoryName_keyword": "玩具", // 单值:精确匹配
- "brandName_keyword": ["乐高", "孩之宝"], // 数组:匹配任意值(OR)
- "in_stock": true // 布尔值
- }
-}
-```
-
-**支持的值类型**:
-- 字符串:精确匹配
-- 整数:精确匹配
-- 布尔值:精确匹配
-- 数组:匹配任意值(OR 逻辑)
-
-##### 范围过滤器 (range_filters)
-
-用于数值字段的范围过滤。
-
-**格式**:
-```json
-{
- "range_filters": {
- "price": {
- "gte": 50, // 大于等于
- "lte": 200 // 小于等于
- },
- "days_since_last_update": {
- "lte": 30 // 最近30天更新
- }
- }
-}
-```
-
-**支持的操作符**:
-- `gte`: 大于等于 (>=)
-- `gt`: 大于 (>)
-- `lte`: 小于等于 (<=)
-- `lt`: 小于 (<)
-
-**注意**: 至少需要指定一个操作符。
-
-##### 分面配置 (facets)
-
-用于生成分面统计(分组聚合)。
-
-**简单模式**(字符串数组):
-```json
-{
- "facets": ["categoryName_keyword", "brandName_keyword"]
-}
-```
-
-**高级模式**(配置对象数组):
-```json
-{
- "facets": [
- {
- "field": "categoryName_keyword",
- "size": 15,
- "type": "terms"
- },
- {
- "field": "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`: 字段名(必填)
-- `size`: 返回的分组数量(默认:10,范围:1-100)
-- `type`: 分面类型,`terms`(分组统计)或 `range`(范围统计)
-- `ranges`: 范围定义(仅当 type='range' 时需要)
-
-#### 响应格式
-
-```json
-{
- "hits": [
- {
- "_id": "12345",
- "_score": 8.5,
- "_custom_score": 12.3,
- "_source": {
- "name": "芭比时尚娃娃",
- "price": 89.99,
- "categoryName": "玩具",
- "brandName": "美泰",
- "imageUrl": "https://example.com/image.jpg"
- }
- }
- ],
- "total": 118,
- "max_score": 8.5,
- "took_ms": 45,
- "facets": [
- {
- "field": "categoryName_keyword",
- "label": "商品类目",
- "type": "terms",
- "values": [
- {
- "value": "玩具",
- "label": "玩具",
- "count": 85,
- "selected": false
- },
- {
- "value": "益智玩具",
- "label": "益智玩具",
- "count": 33,
- "selected": false
- }
- ]
- }
- ],
- "query_info": {
- "original_query": "芭比娃娃",
- "detected_language": "zh",
- "translations": {
- "en": "barbie doll"
- }
- },
- "related_queries": null,
- "performance_info": {
- "total_duration": 45.2,
- "stage_durations": {
- "query_parsing": 5.3,
- "elasticsearch_search": 35.1,
- "result_processing": 4.8
- }
- },
- "debug_info": null
-}
-```
-
-#### 响应字段说明
-
-| 字段 | 类型 | 描述 |
-|------|------|------|
-| `hits` | array | 搜索结果列表 |
-| `hits[]._id` | string | 文档ID |
-| `hits[]._score` | float | 相关性分数 |
-| `hits[]._custom_score` | float | 自定义排序分数(如启用) |
-| `hits[]._source` | object | 文档内容 |
-| `total` | integer | 匹配的总文档数 |
-| `max_score` | float | 最高相关性分数 |
-| `took_ms` | integer | 搜索耗时(毫秒) |
-| `facets` | array | 分面统计结果(标准化格式) |
-| `facets[].field` | string | 字段名 |
-| `facets[].label` | string | 显示标签 |
-| `facets[].type` | string | 分面类型:`terms` 或 `range` |
-| `facets[].values` | array | 分面值列表 |
-| `facets[].values[].value` | any | 分面值 |
-| `facets[].values[].label` | string | 显示标签 |
-| `facets[].values[].count` | integer | 文档数量 |
-| `facets[].values[].selected` | boolean | 是否已选中 |
-| `query_info` | object | 查询处理信息 |
-| `related_queries` | array | 相关搜索(预留) |
-| `performance_info` | object | 性能信息 |
-| `debug_info` | object | 调试信息(仅当 debug=true) |
-
-#### 请求示例
-
-**示例 1: 简单搜索**
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "芭比娃娃",
- "size": 20
- }'
-```
-
-**示例 2: 带过滤器的搜索**
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具",
- "size": 20,
- "filters": {
- "categoryName_keyword": ["玩具", "益智玩具"],
- "in_stock": true
- },
- "range_filters": {
- "price": {
- "gte": 50,
- "lte": 200
- }
- }
- }'
-```
-
-**示例 3: 带分面搜索(简单模式)**
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具",
- "size": 20,
- "facets": ["categoryName_keyword", "brandName_keyword"]
- }'
-```
-
-**示例 4: 带分面搜索(高级模式)**
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具",
- "size": 20,
- "facets": [
- {
- "field": "categoryName_keyword",
- "size": 15,
- "type": "terms"
- },
- {
- "field": "brandName_keyword",
- "size": 15,
- "type": "terms"
- },
- {
- "field": "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}
- ]
- }
- ]
- }'
-```
-
-**示例 5: 复杂搜索(布尔表达式+过滤+排序)**
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具 AND (乐高 OR 芭比)",
- "size": 20,
- "filters": {
- "categoryName_keyword": "玩具"
- },
- "range_filters": {
- "price": {
- "gte": 50,
- "lte": 200
- },
- "days_since_last_update": {
- "lte": 30
- }
- },
- "facets": [
- {"field": "brandName_keyword", "size": 15},
- {"field": "supplierName_keyword", "size": 10}
- ],
- "sort_by": "min_price",
- "sort_order": "asc",
- "debug": false
- }'
-```
-
----
-
-### 2. 图片搜索
-
-**端点**: `POST /search/image`
-
-**描述**: 基于图片相似度进行搜索,使用图片向量进行语义匹配。
-
-#### 请求参数
-
-```json
-{
- "image_url": "string (required)",
- "size": 10,
- "filters": {},
- "range_filters": {}
-}
-```
-
-#### 参数说明
-
-| 参数 | 类型 | 必填 | 默认值 | 描述 |
-|------|------|------|--------|------|
-| `image_url` | string | ✅ | - | 查询图片的 URL |
-| `size` | integer | ❌ | 10 | 返回结果数量(1-100) |
-| `filters` | object | ❌ | null | 精确匹配过滤器 |
-| `range_filters` | object | ❌ | null | 数值范围过滤器 |
-
-#### 响应格式
-
-与文本搜索相同,但 `query_info` 包含图片信息:
-
-```json
-{
- "hits": [...],
- "total": 50,
- "max_score": 0.95,
- "took_ms": 120,
- "query_info": {
- "image_url": "https://example.com/image.jpg",
- "search_type": "image_similarity"
- }
-}
-```
-
-#### 请求示例
-
-```bash
-curl -X POST "http://localhost:6002/search/image" \
- -H "Content-Type: application/json" \
- -d '{
- "image_url": "https://example.com/barbie.jpg",
- "size": 20,
- "filters": {
- "categoryName_keyword": "玩具"
- },
- "range_filters": {
- "price": {
- "lte": 100
- }
- }
- }'
-```
-
----
-
-### 3. 搜索建议(框架)
-
-**端点**: `GET /search/suggestions`
-
-**描述**: 获取搜索建议(自动补全)。
-
-**注意**: 此功能暂未实现,仅返回框架响应。
-
-#### 查询参数
-
-| 参数 | 类型 | 必填 | 默认值 | 描述 |
-|------|------|------|--------|------|
-| `q` | string | ✅ | - | 搜索查询字符串(最少1个字符) |
-| `size` | integer | ❌ | 5 | 建议数量(1-20) |
-| `types` | string | ❌ | "query" | 建议类型(逗号分隔):query, product, category, brand |
-
-#### 响应格式
-
-```json
-{
- "query": "芭",
- "suggestions": [
- {
- "text": "芭比娃娃",
- "type": "query",
- "highlight": "芭比娃娃",
- "popularity": 850
- }
- ],
- "took_ms": 5
-}
-```
-
-#### 请求示例
-
-```bash
-curl "http://localhost:6002/search/suggestions?q=芭&size=5&types=query,product"
-```
-
----
-
-### 4. 即时搜索(框架)
-
-**端点**: `GET /search/instant`
-
-**描述**: 即时搜索,边输入边搜索。
-
-**注意**: 此功能暂未实现,调用标准搜索接口。
-
-#### 查询参数
-
-| 参数 | 类型 | 必填 | 默认值 | 描述 |
-|------|------|------|--------|------|
-| `q` | string | ✅ | - | 搜索查询(最少2个字符) |
-| `size` | integer | ❌ | 5 | 结果数量(1-20) |
-
-#### 请求示例
-
-```bash
-curl "http://localhost:6002/search/instant?q=玩具&size=5"
-```
-
----
-
-### 5. 获取单个文档
-
-**端点**: `GET /search/{doc_id}`
-
-**描述**: 根据文档ID获取单个文档详情。
-
-#### 路径参数
-
-| 参数 | 类型 | 描述 |
-|------|------|------|
-| `doc_id` | string | 文档ID |
-
-#### 响应格式
-
-```json
-{
- "id": "12345",
- "source": {
- "name": "芭比时尚娃娃",
- "price": 89.99,
- "categoryName": "玩具"
- }
-}
-```
-
-#### 请求示例
-
-```bash
-curl "http://localhost:6002/search/12345"
-```
-
----
-
-## 管理接口
-
-### 1. 健康检查
-
-**端点**: `GET /admin/health`
-
-**描述**: 检查服务健康状态。
-
-#### 响应格式
-
-```json
-{
- "status": "healthy",
- "elasticsearch": "connected",
- "tenant_id": "tenant1"
-}
-```
-
----
-
-### 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
-}
-```
-
----
-
-### 3. 索引统计
-
-**端点**: `GET /admin/stats`
-
-**描述**: 获取索引统计信息。
-
-#### 响应格式
-
-```json
-{
- "index_name": "search_tenant1",
- "document_count": 10000,
- "size_mb": 523.45
-}
-```
-
----
-
-### 4. 查询改写规则
-
-**端点**: `GET /admin/rewrite-rules`
-
-**描述**: 获取当前的查询改写规则。
-
-#### 响应格式
-
-```json
-{
- "rules": {
- "乐高": "brand:乐高 OR name:乐高",
- "玩具": "category:玩具"
- },
- "count": 2
-}
-```
-
-**端点**: `POST /admin/rewrite-rules`
-
-**描述**: 更新查询改写规则。
-
-#### 请求格式
-
-```json
-{
- "乐高": "brand:乐高 OR name:乐高",
- "芭比": "brand:芭比 OR name:芭比"
-}
-```
-
----
-
-## 使用示例
-
-### Python 示例
-
-```python
-import requests
-
-API_URL = "http://localhost:6002/search/"
-
-# 简单搜索
-response = requests.post(API_URL, json={
- "query": "芭比娃娃",
- "size": 20
-})
-data = response.json()
-print(f"找到 {data['total']} 个结果")
-
-# 带过滤器和分面的搜索
-response = requests.post(API_URL, json={
- "query": "玩具",
- "size": 20,
- "filters": {
- "categoryName_keyword": ["玩具", "益智玩具"]
- },
- "range_filters": {
- "price": {"gte": 50, "lte": 200}
- },
- "facets": [
- {"field": "brandName_keyword", "size": 15},
- {"field": "categoryName_keyword", "size": 15}
- ],
- "sort_by": "min_price",
- "sort_order": "asc"
-})
-result = response.json()
-
-# 处理分面结果
-for facet in result.get('facets', []):
- print(f"\n{facet['label']}:")
- for value in facet['values']:
- print(f" - {value['label']}: {value['count']}")
-```
-
-### JavaScript 示例
-
-```javascript
-// 搜索函数
-async function searchProducts(query, filters, rangeFilters, facets) {
- const response = await fetch('http://localhost:6002/search/', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- query: query,
- size: 20,
- filters: filters,
- range_filters: rangeFilters,
- facets: facets
- })
- });
-
- const data = await response.json();
- return data;
-}
-
-// 使用示例
-const result = await searchProducts(
- "玩具",
- { categoryName_keyword: ["玩具"] },
- { price: { gte: 50, lte: 200 } },
- [
- { field: "brandName_keyword", size: 15 },
- { field: "categoryName_keyword", size: 15 }
- ]
-);
-
-// 显示分面结果
-result.facets.forEach(facet => {
- console.log(`${facet.label}:`);
- facet.values.forEach(value => {
- console.log(` - ${value.label}: ${value.count}`);
- });
-});
-
-// 显示搜索结果
-result.hits.forEach(hit => {
- const product = hit._source;
- console.log(`${product.name} - ¥${product.price}`);
-});
-```
-
-### cURL 示例
-
-```bash
-# 简单搜索
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{"query": "芭比娃娃", "size": 20}'
-
-# 带过滤和排序
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具",
- "size": 20,
- "filters": {"categoryName_keyword": "玩具"},
- "range_filters": {"price": {"gte": 50, "lte": 200}},
- "sort_by": "min_price",
- "sort_order": "asc"
- }'
-
-# 带分面搜索
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具",
- "size": 20,
- "facets": [
- {"field": "categoryName_keyword", "size": 15},
- {"field": "brandName_keyword", "size": 15}
- ]
- }'
-```
-
----
-
-## 布尔表达式语法
-
-### 支持的操作符
-
-| 操作符 | 描述 | 示例 |
-|--------|------|------|
-| `AND` | 所有词必须匹配 | `玩具 AND 乐高` |
-| `OR` | 任意词匹配 | `芭比 OR 娃娃` |
-| `ANDNOT` | 排除特定词 | `玩具 ANDNOT 电动` |
-| `RANK` | 排序加权(不强制匹配) | `玩具 RANK 乐高` |
-| `()` | 分组 | `玩具 AND (乐高 OR 芭比)` |
-
-### 操作符优先级
-
-从高到低:
-1. `()` - 括号
-2. `ANDNOT` - 排除
-3. `AND` - 与
-4. `OR` - 或
-5. `RANK` - 排序
-
-### 查询示例
-
-```
-# 简单查询
-"芭比娃娃"
-
-# AND 查询
-"玩具 AND 乐高"
-
-# OR 查询
-"芭比 OR 娃娃"
-
-# 排除查询
-"玩具 ANDNOT 电动"
-
-# 复杂查询
-"玩具 AND (乐高 OR 芭比) ANDNOT 电动"
-
-# 域查询
-"brand:乐高"
-"category:玩具"
-"title:芭比娃娃"
-```
-
----
-
-## 数据模型
-
-### 商品字段
-
-常见的商品字段包括:
-
-| 字段名 | 类型 | 描述 |
-|--------|------|------|
-| `skuId` | long | SKU ID(主键) |
-| `name` | text | 商品名称(中文) |
-| `enSpuName` | text | 商品名称(英文) |
-| `ruSkuName` | text | 商品名称(俄文) |
-| `categoryName` | text | 类目名称 |
-| `categoryName_keyword` | keyword | 类目名称(精确匹配) |
-| `brandName` | text | 品牌名称 |
-| `brandName_keyword` | keyword | 品牌名称(精确匹配) |
-| `supplierName` | text | 供应商名称 |
-| `supplierName_keyword` | keyword | 供应商名称(精确匹配) |
-| `price` | double | 价格 |
-| `imageUrl` | keyword | 商品图片URL |
-| `create_time` | date | 创建时间 |
-| `days_since_last_update` | int | 距上次更新天数 |
-
-**注意**: 不同客户可能有不同的字段配置。
-
----
-
-
-## 常见问题
-
-### Q1: 如何判断一个字段应该用哪种过滤器?
-
-**A**:
-- **精确匹配过滤器** (`filters`): 用于 KEYWORD 类型字段(如类目、品牌、标签等)
-- **范围过滤器** (`range_filters`): 用于数值类型字段(如价格、库存、时间等)
-
-### Q2: 可以同时使用多个过滤器吗?
-
-**A**: 可以。多个过滤器之间是 AND 关系(必须同时满足)。
-
-```json
-{
- "filters": {
- "categoryName_keyword": "玩具",
- "brandName_keyword": "乐高"
- },
- "range_filters": {
- "price": {"gte": 50, "lte": 200}
- }
-}
-```
-结果:类目是"玩具" **并且** 品牌是"乐高" **并且** 价格在50-200之间。
-
-### Q3: 如何实现"价格小于50或大于200"的过滤?
-
-**A**: 当前版本不支持单字段多个不连续范围。建议分两次查询或使用布尔查询。
-
-### Q4: 分面搜索返回的 selected 字段是什么意思?
-
-**A**: `selected` 表示该分面值是否在当前的过滤器中。前端可以用它来高亮已选中的过滤项。
-
-### Q5: 如何使用自定义排序?
-
-**A**: 使用 `sort_by` 和 `sort_order` 参数:
-
-```json
-{
- "query": "玩具",
- "sort_by": "price",
- "sort_order": "asc" // 价格从低到高
-}
-```
-
-常用排序字段:
-- `price`: 价格
-- `create_time`: 创建时间
-- `days_since_last_update`: 更新时间
-
-### Q6: 如何启用调试模式?
-
-**A**: 设置 `debug: true`,响应中会包含 `debug_info` 字段,包含:
-- 查询分析过程
-- ES 查询 DSL
-- ES 响应详情
-- 各阶段耗时
-
----
-
-## 版本历史
-
-### v3.0 (2024-11-12)
-
-**重大更新**:
-- ✅ 移除硬编码的 `price_ranges` 逻辑
-- ✅ 新增 `range_filters` 参数,支持任意数值字段的范围过滤
-- ✅ 新增 `facets` 参数,替代 `aggregations`,提供简化接口
-- ✅ 标准化分面搜索响应格式
-- ✅ 新增 `/search/suggestions` 端点(框架)
-- ✅ 新增 `/search/instant` 端点(框架)
-- ❌ **移除** `aggregations` 参数(不向后兼容)
-
-**迁移指南**:
-
-旧接口:
-```json
-{
- "filters": {
- "price_ranges": ["0-50", "50-100"]
- },
- "aggregations": {
- "category_stats": {"terms": {"field": "categoryName_keyword", "size": 15}}
- }
-}
-```
-
-新接口:
-```json
-{
- "range_filters": {
- "price": {"gte": 50, "lte": 100}
- },
- "facets": [
- {"field": "categoryName_keyword", "size": 15}
- ]
-}
-```
-
----
-
-## 附录
-
-### A. 支持的分析器
-
-| 分析器 | 语言 | 描述 |
-|--------|------|------|
-| `chinese_ecommerce` | 中文 | Ansj 中文分词器(电商优化) |
-| `english` | 英文 | 标准英文分析器 |
-| `russian` | 俄文 | 俄文分析器 |
-| `arabic` | 阿拉伯文 | 阿拉伯文分析器 |
-| `spanish` | 西班牙文 | 西班牙文分析器 |
-| `japanese` | 日文 | 日文分析器 |
-
-### B. 字段类型
-
-| 类型 | ES 映射 | 用途 |
-|------|---------|------|
-| `TEXT` | text | 全文检索 |
-| `KEYWORD` | keyword | 精确匹配、聚合、排序 |
-| `LONG` | long | 整数 |
-| `DOUBLE` | double | 浮点数 |
-| `DATE` | date | 日期时间 |
-| `BOOLEAN` | boolean | 布尔值 |
-| `TEXT_EMBEDDING` | dense_vector | 文本向量(1024维) |
-| `IMAGE_EMBEDDING` | dense_vector | 图片向量(1024维) |
-
diff --git a/API_EXAMPLES.md b/API_EXAMPLES.md
deleted file mode 100644
index c262fa2..0000000
--- a/API_EXAMPLES.md
+++ /dev/null
@@ -1,1148 +0,0 @@
-# API 使用示例
-
-本文档提供了搜索引擎 API 的详细使用示例,包括各种常见场景和最佳实践。
-
----
-
-## 目录
-
-1. [基础搜索](#基础搜索)
-2. [过滤器使用](#过滤器使用)
-3. [分面搜索](#分面搜索)
-4. [排序](#排序)
-5. [图片搜索](#图片搜索)
-6. [布尔表达式](#布尔表达式)
-7. [完整示例](#完整示例)
-
----
-
-## 基础搜索
-
-### 示例 1:最简单的搜索
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "芭比娃娃"
- }'
-```
-
-**响应**:
-```json
-{
- "hits": [...],
- "total": 118,
- "max_score": 8.5,
- "took_ms": 45,
- "query_info": {
- "original_query": "芭比娃娃",
- "detected_language": "zh",
- "translations": {"en": "barbie doll"}
- }
-}
-```
-
-### 示例 2:指定返回数量
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具",
- "size": 50
- }'
-```
-
-### 示例 3:分页查询
-
-```bash
-# 第1页(0-19)
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具",
- "size": 20,
- "from": 0
- }'
-
-# 第2页(20-39)
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具",
- "size": 20,
- "from": 20
- }'
-```
-
----
-
-## 过滤器使用
-
-### 精确匹配过滤器
-
-#### 示例 1:单值过滤
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具",
- "filters": {
- "categoryName_keyword": "玩具"
- }
- }'
-```
-
-#### 示例 2:多值过滤(OR 逻辑)
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "娃娃",
- "filters": {
- "categoryName_keyword": ["玩具", "益智玩具", "儿童玩具"]
- }
- }'
-```
-
-说明:匹配类目为"玩具" **或** "益智玩具" **或** "儿童玩具"的商品。
-
-#### 示例 3:多字段过滤(AND 逻辑)
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "娃娃",
- "filters": {
- "categoryName_keyword": "玩具",
- "brandName_keyword": "美泰"
- }
- }'
-```
-
-说明:必须同时满足"类目=玩具" **并且** "品牌=美泰"。
-
-### 范围过滤器
-
-#### 示例 1:价格范围
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具",
- "range_filters": {
- "price": {
- "gte": 50,
- "lte": 200
- }
- }
- }'
-```
-
-说明:价格在 50-200 元之间(包含边界)。
-
-#### 示例 2:只设置下限
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具",
- "range_filters": {
- "price": {
- "gte": 100
- }
- }
- }'
-```
-
-说明:价格 ≥ 100 元。
-
-#### 示例 3:只设置上限
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具",
- "range_filters": {
- "price": {
- "lt": 50
- }
- }
- }'
-```
-
-说明:价格 < 50 元(不包含50)。
-
-#### 示例 4:多字段范围过滤
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具",
- "range_filters": {
- "price": {
- "gte": 50,
- "lte": 200
- },
- "days_since_last_update": {
- "lte": 30
- }
- }
- }'
-```
-
-说明:价格在 50-200 元 **并且** 最近30天内更新过。
-
-### 组合过滤器
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具",
- "filters": {
- "categoryName_keyword": ["玩具", "益智玩具"],
- "brandName_keyword": "乐高"
- },
- "range_filters": {
- "price": {
- "gte": 50,
- "lte": 500
- }
- }
- }'
-```
-
-说明:类目是"玩具"或"益智玩具" **并且** 品牌是"乐高" **并且** 价格在 50-500 元之间。
-
----
-
-## 分面搜索
-
-### 简单模式
-
-#### 示例 1:基础分面
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具",
- "size": 20,
- "facets": ["categoryName_keyword", "brandName_keyword"]
- }'
-```
-
-**响应**:
-```json
-{
- "hits": [...],
- "total": 118,
- "facets": [
- {
- "field": "categoryName_keyword",
- "label": "categoryName_keyword",
- "type": "terms",
- "values": [
- {"value": "玩具", "count": 85, "selected": false},
- {"value": "益智玩具", "count": 33, "selected": false}
- ]
- },
- {
- "field": "brandName_keyword",
- "label": "brandName_keyword",
- "type": "terms",
- "values": [
- {"value": "乐高", "count": 42, "selected": false},
- {"value": "美泰", "count": 28, "selected": false}
- ]
- }
- ]
-}
-```
-
-### 高级模式
-
-#### 示例 1:自定义分面大小
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具",
- "facets": [
- {
- "field": "categoryName_keyword",
- "size": 20,
- "type": "terms"
- },
- {
- "field": "brandName_keyword",
- "size": 30,
- "type": "terms"
- }
- ]
- }'
-```
-
-#### 示例 2:范围分面
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具",
- "facets": [
- {
- "field": "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}
- ]
- }
- ]
- }'
-```
-
-**响应**:
-```json
-{
- "facets": [
- {
- "field": "price",
- "label": "price",
- "type": "range",
- "values": [
- {"value": "0-50", "count": 23, "selected": false},
- {"value": "50-100", "count": 45, "selected": false},
- {"value": "100-200", "count": 38, "selected": false},
- {"value": "200+", "count": 12, "selected": false}
- ]
- }
- ]
-}
-```
-
-#### 示例 3:混合分面(Terms + Range)
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具",
- "facets": [
- {"field": "categoryName_keyword", "size": 15},
- {"field": "brandName_keyword", "size": 15},
- {
- "field": "price",
- "type": "range",
- "ranges": [
- {"key": "低价", "to": 50},
- {"key": "中价", "from": 50, "to": 200},
- {"key": "高价", "from": 200}
- ]
- }
- ]
- }'
-```
-
----
-
-## 排序
-
-### 示例 1:按价格排序(升序)
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具",
- "size": 20,
- "sort_by": "min_price",
- "sort_order": "asc"
- }'
-```
-
-### 示例 2:按创建时间排序(降序)
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具",
- "size": 20,
- "sort_by": "create_time",
- "sort_order": "desc"
- }'
-```
-
-### 示例 3:排序+过滤
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具",
- "filters": {
- "categoryName_keyword": "益智玩具"
- },
- "sort_by": "min_price",
- "sort_order": "asc"
- }'
-```
-
----
-
-## 图片搜索
-
-### 示例 1:基础图片搜索
-
-```bash
-curl -X POST "http://localhost:6002/search/image" \
- -H "Content-Type: application/json" \
- -d '{
- "image_url": "https://example.com/barbie.jpg",
- "size": 20
- }'
-```
-
-### 示例 2:图片搜索+过滤器
-
-```bash
-curl -X POST "http://localhost:6002/search/image" \
- -H "Content-Type: application/json" \
- -d '{
- "image_url": "https://example.com/barbie.jpg",
- "size": 20,
- "filters": {
- "categoryName_keyword": "玩具"
- },
- "range_filters": {
- "price": {
- "lte": 100
- }
- }
- }'
-```
-
----
-
-## 布尔表达式
-
-### 示例 1:AND 查询
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具 AND 乐高"
- }'
-```
-
-说明:必须同时包含"玩具"和"乐高"。
-
-### 示例 2:OR 查询
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "芭比 OR 娃娃"
- }'
-```
-
-说明:包含"芭比"或"娃娃"即可。
-
-### 示例 3:ANDNOT 查询(排除)
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具 ANDNOT 电动"
- }'
-```
-
-说明:包含"玩具"但不包含"电动"。
-
-### 示例 4:复杂布尔表达式
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具 AND (乐高 OR 芭比) ANDNOT 电动"
- }'
-```
-
-说明:必须包含"玩具",并且包含"乐高"或"芭比",但不包含"电动"。
-
-### 示例 5:域查询
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "brand:乐高"
- }'
-```
-
-说明:在品牌域中搜索"乐高"。
-
----
-
-## 完整示例
-
-### Python 完整示例
-
-```python
-#!/usr/bin/env python3
-import requests
-import json
-
-API_URL = "http://localhost:6002/search/"
-
-def search_products(
- query,
- size=20,
- from_=0,
- filters=None,
- range_filters=None,
- facets=None,
- sort_by=None,
- sort_order="desc",
- debug=False
-):
- """执行搜索查询"""
- payload = {
- "query": query,
- "size": size,
- "from": from_
- }
-
- if filters:
- payload["filters"] = filters
- if range_filters:
- payload["range_filters"] = range_filters
- if facets:
- payload["facets"] = facets
- if sort_by:
- payload["sort_by"] = sort_by
- payload["sort_order"] = sort_order
- if debug:
- payload["debug"] = debug
-
- response = requests.post(API_URL, json=payload)
- response.raise_for_status()
- return response.json()
-
-
-# 示例 1:简单搜索
-result = search_products("芭比娃娃", size=10)
-print(f"找到 {result['total']} 个结果")
-for hit in result['hits'][:3]:
- product = hit['_source']
- print(f" - {product['name']}: ¥{product.get('price', 'N/A')}")
-
-# 示例 2:带过滤和分面的搜索
-result = search_products(
- query="玩具",
- size=20,
- filters={
- "categoryName_keyword": ["玩具", "益智玩具"]
- },
- range_filters={
- "price": {"gte": 50, "lte": 200}
- },
- facets=[
- {"field": "brandName_keyword", "size": 15},
- {"field": "categoryName_keyword", "size": 15},
- {
- "field": "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}
- ]
- }
- ],
- sort_by="min_price",
- sort_order="asc"
-)
-
-# 显示分面结果
-print(f"\n分面统计:")
-for facet in result.get('facets', []):
- print(f"\n{facet['label']} ({facet['type']}):")
- for value in facet['values'][:5]:
- selected_mark = "✓" if value['selected'] else " "
- print(f" [{selected_mark}] {value['label']}: {value['count']}")
-
-# 示例 3:分页查询
-page = 1
-page_size = 20
-total_pages = 5
-
-for page in range(1, total_pages + 1):
- result = search_products(
- query="玩具",
- size=page_size,
- from_=(page - 1) * page_size
- )
- print(f"\n第 {page} 页:")
- for hit in result['hits']:
- product = hit['_source']
- print(f" - {product['name']}")
-```
-
-### JavaScript 完整示例
-
-```javascript
-// 搜索引擎客户端
-class SearchClient {
- constructor(baseUrl) {
- this.baseUrl = baseUrl;
- }
-
- async search({
- query,
- size = 20,
- from = 0,
- filters = null,
- rangeFilters = null,
- facets = null,
- sortBy = null,
- sortOrder = 'desc',
- debug = false
- }) {
- const payload = {
- query,
- size,
- from
- };
-
- if (filters) payload.filters = filters;
- if (rangeFilters) payload.range_filters = rangeFilters;
- if (facets) payload.facets = facets;
- if (sortBy) {
- payload.sort_by = sortBy;
- payload.sort_order = sortOrder;
- }
- if (debug) payload.debug = debug;
-
- const response = await fetch(`${this.baseUrl}/search/`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(payload)
- });
-
- if (!response.ok) {
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
- }
-
- return await response.json();
- }
-
- async searchByImage(imageUrl, options = {}) {
- const payload = {
- image_url: imageUrl,
- size: options.size || 20,
- filters: options.filters || null,
- range_filters: options.rangeFilters || null
- };
-
- const response = await fetch(`${this.baseUrl}/search/image`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(payload)
- });
-
- if (!response.ok) {
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
- }
-
- return await response.json();
- }
-}
-
-// 使用示例
-const client = new SearchClient('http://localhost:6002');
-
-// 简单搜索
-const result1 = await client.search({
- query: "芭比娃娃",
- size: 20
-});
-console.log(`找到 ${result1.total} 个结果`);
-
-// 带过滤和分面的搜索
-const result2 = await client.search({
- query: "玩具",
- size: 20,
- filters: {
- categoryName_keyword: ["玩具", "益智玩具"]
- },
- rangeFilters: {
- price: { gte: 50, lte: 200 }
- },
- facets: [
- { field: "brandName_keyword", size: 15 },
- { field: "categoryName_keyword", size: 15 }
- ],
- sortBy: "price",
- sortOrder: "asc"
-});
-
-// 显示分面结果
-result2.facets.forEach(facet => {
- console.log(`\n${facet.label}:`);
- facet.values.forEach(value => {
- const selected = value.selected ? '✓' : ' ';
- console.log(` [${selected}] ${value.label}: ${value.count}`);
- });
-});
-
-// 显示商品
-result2.hits.forEach(hit => {
- const product = hit._source;
- console.log(`${product.name} - ¥${product.price}`);
-});
-```
-
-### 前端完整示例(Vue.js 风格)
-
-```javascript
-// 搜索组件
-const SearchComponent = {
- data() {
- return {
- query: '',
- results: [],
- facets: [],
- filters: {},
- rangeFilters: {},
- total: 0,
- currentPage: 1,
- pageSize: 20
- };
- },
- methods: {
- async search() {
- const response = await fetch('http://localhost:6002/search/', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- query: this.query,
- size: this.pageSize,
- from: (this.currentPage - 1) * this.pageSize,
- filters: this.filters,
- range_filters: this.rangeFilters,
- facets: [
- { field: 'categoryName_keyword', size: 15 },
- { field: 'brandName_keyword', size: 15 }
- ]
- })
- });
-
- const data = await response.json();
- this.results = data.hits;
- this.facets = data.facets || [];
- this.total = data.total;
- },
-
- toggleFilter(field, value) {
- if (!this.filters[field]) {
- this.filters[field] = [];
- }
-
- const index = this.filters[field].indexOf(value);
- if (index > -1) {
- this.filters[field].splice(index, 1);
- if (this.filters[field].length === 0) {
- delete this.filters[field];
- }
- } else {
- this.filters[field].push(value);
- }
-
- this.currentPage = 1;
- this.search();
- },
-
- setPriceRange(min, max) {
- if (min !== null || max !== null) {
- this.rangeFilters.price = {};
- if (min !== null) this.rangeFilters.price.gte = min;
- if (max !== null) this.rangeFilters.price.lte = max;
- } else {
- delete this.rangeFilters.price;
- }
- this.currentPage = 1;
- this.search();
- }
- }
-};
-```
-
----
-
-## 调试与优化
-
-### 启用调试模式
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具",
- "debug": true
- }'
-```
-
-**响应包含调试信息**:
-```json
-{
- "hits": [...],
- "total": 118,
- "debug_info": {
- "query_analysis": {
- "original_query": "玩具",
- "normalized_query": "玩具",
- "rewritten_query": "玩具",
- "detected_language": "zh",
- "translations": {"en": "toy"}
- },
- "es_query": {
- "query": {...},
- "size": 10
- },
- "stage_timings": {
- "query_parsing": 5.3,
- "elasticsearch_search": 35.1,
- "result_processing": 4.8
- }
- }
-}
-```
-
-### 设置最小分数阈值
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具",
- "min_score": 5.0
- }'
-```
-
-说明:只返回相关性分数 ≥ 5.0 的结果。
-
----
-
-## 常见使用场景
-
-### 场景 1:电商分类页
-
-```bash
-# 显示某个类目下的所有商品,按价格排序,提供品牌筛选
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "*",
- "filters": {
- "categoryName_keyword": "玩具"
- },
- "facets": [
- {"field": "brandName_keyword", "size": 20},
- {
- "field": "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}
- ]
- }
- ],
- "sort_by": "min_price",
- "sort_order": "asc",
- "size": 24
- }'
-```
-
-### 场景 2:搜索结果页
-
-```bash
-# 用户搜索关键词,提供筛选和排序
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "芭比娃娃",
- "facets": [
- {"field": "categoryName_keyword", "size": 10},
- {"field": "brandName_keyword", "size": 10},
- {"field": "price", "type": "range", "ranges": [
- {"key": "0-50", "to": 50},
- {"key": "50-100", "from": 50, "to": 100},
- {"key": "100+", "from": 100}
- ]}
- ],
- "size": 20
- }'
-```
-
-### 场景 3:促销专区
-
-```bash
-# 显示特定价格区间的商品
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "*",
- "range_filters": {
- "price": {
- "gte": 50,
- "lte": 100
- }
- },
- "facets": ["categoryName_keyword", "brandName_keyword"],
- "sort_by": "min_price",
- "sort_order": "asc",
- "size": 50
- }'
-```
-
-### 场景 4:新品推荐
-
-```bash
-# 最近更新的商品
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "*",
- "range_filters": {
- "days_since_last_update": {
- "lte": 7
- }
- },
- "sort_by": "create_time",
- "sort_order": "desc",
- "size": 20
- }'
-```
-
----
-
-## 错误处理
-
-### 示例 1:参数错误
-
-```bash
-# 错误:range_filters 缺少操作符
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具",
- "range_filters": {
- "price": {}
- }
- }'
-```
-
-**响应**:
-```json
-{
- "error": "Validation error",
- "detail": "至少需要指定一个范围边界(gte, gt, lte, lt)",
- "timestamp": 1699800000
-}
-```
-
-### 示例 2:空查询
-
-```bash
-# 错误:query 为空
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": ""
- }'
-```
-
-**响应**:
-```json
-{
- "error": "Validation error",
- "detail": "query field required",
- "timestamp": 1699800000
-}
-```
-
----
-
-## 性能优化建议
-
-### 1. 合理使用分面
-
-```bash
-# ❌ 不推荐:请求太多分面
-{
- "facets": [
- {"field": "field1", "size": 100},
- {"field": "field2", "size": 100},
- {"field": "field3", "size": 100},
- // ... 10+ facets
- ]
-}
-
-# ✅ 推荐:只请求必要的分面
-{
- "facets": [
- {"field": "categoryName_keyword", "size": 15},
- {"field": "brandName_keyword", "size": 15}
- ]
-}
-```
-
-### 2. 控制返回数量
-
-```bash
-# ❌ 不推荐:一次返回太多
-{
- "size": 100
-}
-
-# ✅ 推荐:分页查询
-{
- "size": 20,
- "from": 0
-}
-```
-
-### 3. 使用适当的过滤器
-
-```bash
-# ✅ 推荐:先过滤后搜索
-{
- "query": "玩具",
- "filters": {
- "categoryName_keyword": "玩具"
- }
-}
-```
-
----
-
-## 高级技巧
-
-### 技巧 1:获取所有类目
-
-```bash
-# 使用通配符查询 + 分面
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "*",
- "size": 0,
- "facets": [
- {"field": "categoryName_keyword", "size": 100}
- ]
- }'
-```
-
-### 技巧 2:价格分布统计
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具",
- "size": 0,
- "facets": [
- {
- "field": "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-500", "from": 200, "to": 500},
- {"key": "500+", "from": 500}
- ]
- }
- ]
- }'
-```
-
-### 技巧 3:组合多种查询类型
-
-```bash
-# 布尔表达式 + 过滤器 + 分面 + 排序
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "(玩具 OR 游戏) AND 儿童 ANDNOT 电子",
- "filters": {
- "categoryName_keyword": ["玩具", "益智玩具"]
- },
- "range_filters": {
- "price": {"gte": 20, "lte": 100},
- "days_since_last_update": {"lte": 30}
- },
- "facets": [
- {"field": "brandName_keyword", "size": 20}
- ],
- "sort_by": "min_price",
- "sort_order": "asc",
- "size": 20
- }'
-```
-
----
-
-## 测试数据
-
-如果你需要测试数据,可以使用以下查询:
-
-```bash
-# 测试类目:玩具
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{"query": "玩具", "size": 5}'
-
-# 测试品牌:乐高
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{"query": "brand:乐高", "size": 5}'
-
-# 测试布尔表达式
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{"query": "玩具 AND 乐高", "size": 5}'
-```
-
----
-
-**文档版本**: 3.0
-**最后更新**: 2024-11-12
-**相关文档**: `API_DOCUMENTATION.md`
-
diff --git a/API_INTEGRATION_GUIDE.md b/API_INTEGRATION_GUIDE.md
deleted file mode 100644
index 1639f83..0000000
--- a/API_INTEGRATION_GUIDE.md
+++ /dev/null
@@ -1,791 +0,0 @@
-# 搜索API接口对接指南
-
-本文档为搜索服务的使用方提供完整的API对接指南,包括接口说明、请求参数、响应格式和使用示例。
-
-## 目录
-
-1. [快速开始](#快速开始)
-2. [接口概览](#接口概览)
-3. [文本搜索接口](#文本搜索接口)
-4. [图片搜索接口](#图片搜索接口)
-5. [响应格式说明](#响应格式说明)
-6. [常见场景示例](#常见场景示例)
-7. [错误处理](#错误处理)
-8. [最佳实践](#最佳实践)
-
----
-
-## 快速开始
-
-### 基础信息
-
-- **Base URL**: `http://your-domain:6002` 或 `http://120.76.41.98:6002`
-- **协议**: HTTP/HTTPS
-- **数据格式**: JSON
-- **字符编码**: UTF-8
-- **请求方法**: POST(搜索接口)
-
-### 最简单的搜索请求
-
-```bash
-curl -X POST "http://localhost:6002/search/" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "芭比娃娃"
- }'
-```
-
-### Python示例
-
-```python
-import requests
-
-url = "http://localhost:6002/search/"
-response = requests.post(url, json={"query": "芭比娃娃"})
-data = response.json()
-print(f"找到 {data['total']} 个结果")
-```
-
-### JavaScript示例
-
-```javascript
-const response = await fetch('http://localhost:6002/search/', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- query: '芭比娃娃'
- })
-});
-const data = await response.json();
-console.log(`找到 ${data.total} 个结果`);
-```
-
----
-
-## 接口概览
-
-| 接口 | 方法 | 路径 | 说明 |
-|------|------|------|------|
-| 文本搜索 | POST | `/search/` | 执行文本搜索查询 |
-| 图片搜索 | POST | `/search/image` | 基于图片相似度搜索 |
-| 搜索建议 | GET | `/search/suggestions` | 获取搜索建议(框架,暂未实现) |
-| 获取文档 | GET | `/search/{doc_id}` | 根据ID获取单个文档 |
-| 健康检查 | GET | `/admin/health` | 检查服务状态 |
-
----
-
-## 文本搜索接口
-
-### 接口信息
-
-- **端点**: `POST /search/`
-- **描述**: 执行文本搜索查询,支持多语言、布尔表达式、过滤器和分面搜索
-
-### 请求参数
-
-#### 完整请求体结构
-
-```json
-{
- "query": "string (required)",
- "size": 10,
- "from": 0,
- "filters": {},
- "range_filters": {},
- "facets": [],
- "sort_by": "string",
- "sort_order": "desc",
- "min_score": 0.0,
- "debug": false,
- "user_id": "string",
- "session_id": "string"
-}
-```
-
-#### 参数详细说明
-
-| 参数 | 类型 | 必填 | 默认值 | 说明 |
-|------|------|------|--------|------|
-| `query` | string | ✅ | - | 搜索查询字符串,支持布尔表达式(AND, OR, RANK, ANDNOT) |
-| `size` | integer | ❌ | 10 | 返回结果数量(1-100) |
-| `from` | integer | ❌ | 0 | 分页偏移量(用于分页) |
-| `filters` | object | ❌ | null | 精确匹配过滤器(见下文) |
-| `range_filters` | object | ❌ | null | 数值范围过滤器(见下文) |
-| `facets` | array | ❌ | null | 分面配置(见下文) |
-| `sort_by` | string | ❌ | null | 排序字段名(如 `min_price`, `max_price`, `title`) |
-| `sort_order` | string | ❌ | "desc" | 排序方向:`asc`(升序)或 `desc`(降序) |
-| `min_score` | float | ❌ | null | 最小相关性分数阈值 |
-| `debug` | boolean | ❌ | false | 是否返回调试信息 |
-| `user_id` | string | ❌ | null | 用户ID(用于个性化,预留) |
-| `session_id` | string | ❌ | null | 会话ID(用于分析,预留) |
-
-### 过滤器详解
-
-#### 1. 精确匹配过滤器 (filters)
-
-用于精确匹配或多值匹配(OR 逻辑)。
-
-**格式**:
-```json
-{
- "filters": {
- "category_keyword": "玩具", // 单值:精确匹配
- "vendor_keyword": ["乐高", "孩之宝"], // 数组:匹配任意值(OR)
- "product_type_keyword": "益智玩具" // 单值:精确匹配
- }
-}
-```
-
-**支持的值类型**:
-- 字符串:精确匹配
-- 整数:精确匹配
-- 布尔值:精确匹配
-- 数组:匹配任意值(OR 逻辑)
-
-**常用过滤字段**:
-- `category_keyword`: 类目
-- `vendor_keyword`: 品牌/供应商
-- `product_type_keyword`: 商品类型
-- `tags_keyword`: 标签
-
-#### 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`: 小于 (<)
-
-**注意**: 至少需要指定一个操作符。
-
-**常用范围字段**:
-- `min_price`: 最低价格
-- `max_price`: 最高价格
-- `compare_at_price`: 原价
-- `create_time`: 创建时间
-- `update_time`: 更新时间
-
-#### 3. 分面配置 (facets)
-
-用于生成分面统计(分组聚合),常用于构建筛选器UI。
-
-**简单模式**(字符串数组):
-```json
-{
- "facets": ["category_keyword", "vendor_keyword"]
-}
-```
-
-**高级模式**(配置对象数组):
-```json
-{
- "facets": [
- {
- "field": "category_keyword",
- "size": 15,
- "type": "terms"
- },
- {
- "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`: 字段名(必填)
-- `size`: 返回的分组数量(默认:10,范围:1-100)
-- `type`: 分面类型,`terms`(分组统计)或 `range`(范围统计)
-- `ranges`: 范围定义(仅当 type='range' 时需要)
-
-### 布尔表达式语法
-
-搜索查询支持布尔表达式,提供更灵活的搜索能力。
-
-**支持的操作符**:
-
-| 操作符 | 描述 | 示例 |
-|--------|------|------|
-| `AND` | 所有词必须匹配 | `玩具 AND 乐高` |
-| `OR` | 任意词匹配 | `芭比 OR 娃娃` |
-| `ANDNOT` | 排除特定词 | `玩具 ANDNOT 电动` |
-| `RANK` | 排序加权(不强制匹配) | `玩具 RANK 乐高` |
-| `()` | 分组 | `玩具 AND (乐高 OR 芭比)` |
-
-**操作符优先级**(从高到低):
-1. `()` - 括号
-2. `ANDNOT` - 排除
-3. `AND` - 与
-4. `OR` - 或
-5. `RANK` - 排序
-
-**示例**:
-```
-"芭比娃娃" // 简单查询
-"玩具 AND 乐高" // AND 查询
-"芭比 OR 娃娃" // OR 查询
-"玩具 ANDNOT 电动" // 排除查询
-"玩具 AND (乐高 OR 芭比)" // 复杂查询
-```
-
----
-
-## 图片搜索接口
-
-### 接口信息
-
-- **端点**: `POST /search/image`
-- **描述**: 基于图片相似度进行搜索,使用图片向量进行语义匹配
-
-### 请求参数
-
-```json
-{
- "image_url": "string (required)",
- "size": 10,
- "filters": {},
- "range_filters": {}
-}
-```
-
-### 参数说明
-
-| 参数 | 类型 | 必填 | 默认值 | 描述 |
-|------|------|------|--------|------|
-| `image_url` | string | ✅ | - | 查询图片的 URL |
-| `size` | integer | ❌ | 10 | 返回结果数量(1-100) |
-| `filters` | object | ❌ | null | 精确匹配过滤器 |
-| `range_filters` | object | ❌ | null | 数值范围过滤器 |
-
-### 请求示例
-
-```bash
-curl -X POST "http://localhost:6002/search/image" \
- -H "Content-Type: application/json" \
- -d '{
- "image_url": "https://example.com/barbie.jpg",
- "size": 20,
- "filters": {
- "category_keyword": "玩具"
- },
- "range_filters": {
- "min_price": {
- "lte": 100
- }
- }
- }'
-```
-
----
-
-## 响应格式说明
-
-### 标准响应结构
-
-```json
-{
- "results": [
- {
- "product_id": "12345",
- "title": "芭比时尚娃娃",
- "handle": "barbie-doll",
- "description": "高品质芭比娃娃",
- "vendor": "美泰",
- "product_type": "玩具",
- "tags": "娃娃, 玩具, 女孩",
- "price": 89.99,
- "compare_at_price": 129.99,
- "currency": "USD",
- "image_url": "https://example.com/image.jpg",
- "in_stock": true,
- "variants": [
- {
- "variant_id": "67890",
- "title": "粉色款",
- "price": 89.99,
- "compare_at_price": 129.99,
- "sku": "BARBIE-001",
- "stock": 100,
- "options": {
- "option1": "粉色",
- "option2": "标准款"
- }
- }
- ],
- "relevance_score": 8.5
- }
- ],
- "total": 118,
- "max_score": 8.5,
- "facets": [
- {
- "field": "category_keyword",
- "label": "category_keyword",
- "type": "terms",
- "values": [
- {
- "value": "玩具",
- "label": "玩具",
- "count": 85,
- "selected": false
- }
- ]
- }
- ],
- "query_info": {
- "original_query": "芭比娃娃",
- "detected_language": "zh",
- "translations": {
- "en": "barbie doll"
- }
- },
- "suggestions": [],
- "related_searches": [],
- "took_ms": 45,
- "performance_info": null,
- "debug_info": null
-}
-```
-
-### 响应字段说明
-
-| 字段 | 类型 | 说明 |
-|------|------|------|
-| `results` | array | 搜索结果列表(ProductResult对象数组) |
-| `results[].product_id` | string | 商品ID |
-| `results[].title` | string | 商品标题 |
-| `results[].price` | float | 价格(min_price) |
-| `results[].variants` | array | 变体列表(SKU列表) |
-| `results[].relevance_score` | float | 相关性分数 |
-| `total` | integer | 匹配的总文档数 |
-| `max_score` | float | 最高相关性分数 |
-| `facets` | array | 分面统计结果 |
-| `query_info` | object | 查询处理信息 |
-| `took_ms` | integer | 搜索耗时(毫秒) |
-
-### ProductResult字段说明
-
-| 字段 | 类型 | 说明 |
-|------|------|------|
-| `product_id` | string | 商品ID(SPU ID) |
-| `title` | string | 商品标题 |
-| `handle` | string | 商品URL handle |
-| `description` | string | 商品描述 |
-| `vendor` | string | 供应商/品牌 |
-| `product_type` | string | 商品类型 |
-| `tags` | string | 标签 |
-| `price` | float | 价格(min_price) |
-| `compare_at_price` | float | 原价 |
-| `currency` | string | 货币单位(默认USD) |
-| `image_url` | string | 主图URL |
-| `in_stock` | boolean | 是否有库存(任意变体有库存即为true) |
-| `variants` | array | 变体列表 |
-| `relevance_score` | float | 相关性分数 |
-
-### VariantResult字段说明
-
-| 字段 | 类型 | 说明 |
-|------|------|------|
-| `variant_id` | string | 变体ID(SKU ID) |
-| `title` | string | 变体标题 |
-| `price` | float | 价格 |
-| `compare_at_price` | float | 原价 |
-| `sku` | string | SKU编码 |
-| `stock` | integer | 库存数量 |
-| `options` | object | 选项(颜色、尺寸等) |
-
----
-
-## 常见场景示例
-
-### 场景1:商品列表页搜索
-
-**需求**: 搜索"玩具",按价格从低到高排序,显示前20个结果
-
-```json
-{
- "query": "玩具",
- "size": 20,
- "from": 0,
- "sort_by": "min_price",
- "sort_order": "asc"
-}
-```
-
-### 场景2:带筛选的商品搜索
-
-**需求**: 搜索"玩具",筛选类目为"益智玩具",价格在50-200之间
-
-```json
-{
- "query": "玩具",
- "size": 20,
- "filters": {
- "category_keyword": "益智玩具"
- },
- "range_filters": {
- "min_price": {
- "gte": 50,
- "lte": 200
- }
- }
-}
-```
-
-### 场景3:带分面的商品搜索
-
-**需求**: 搜索"玩具",获取类目和品牌的分面统计,用于构建筛选器
-
-```json
-{
- "query": "玩具",
- "size": 20,
- "facets": [
- "category_keyword",
- "vendor_keyword"
- ]
-}
-```
-
-### 场景4:多条件组合搜索
-
-**需求**: 搜索"玩具",筛选多个品牌,价格范围,并获取分面统计
-
-```json
-{
- "query": "玩具",
- "size": 20,
- "filters": {
- "vendor_keyword": ["乐高", "孩之宝", "美泰"]
- },
- "range_filters": {
- "min_price": {
- "gte": 50,
- "lte": 200
- }
- },
- "facets": [
- {
- "field": "category_keyword",
- "size": 15
- },
- {
- "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}
- ]
- }
- ],
- "sort_by": "min_price",
- "sort_order": "asc"
-}
-```
-
-### 场景5:布尔表达式搜索
-
-**需求**: 搜索包含"玩具"和"乐高"的商品,排除"电动"
-
-```json
-{
- "query": "玩具 AND 乐高 ANDNOT 电动",
- "size": 20
-}
-```
-
-### 场景6:分页查询
-
-**需求**: 获取第2页结果(每页20条)
-
-```json
-{
- "query": "玩具",
- "size": 20,
- "from": 20
-}
-```
-
----
-
-## 错误处理
-
-### 错误响应格式
-
-```json
-{
- "error": "错误信息",
- "detail": "详细错误信息(可选)"
-}
-```
-
-### 常见错误码
-
-| HTTP状态码 | 说明 | 处理建议 |
-|-----------|------|---------|
-| 200 | 成功 | - |
-| 400 | 请求参数错误 | 检查请求参数格式和必填字段 |
-| 404 | 接口不存在 | 检查接口路径 |
-| 500 | 服务器内部错误 | 联系技术支持 |
-
-### 错误处理示例
-
-**Python**:
-```python
-import requests
-
-try:
- response = requests.post(url, json=payload, timeout=10)
- response.raise_for_status()
- data = response.json()
-except requests.exceptions.HTTPError as e:
- print(f"HTTP错误: {e}")
- if response.status_code == 400:
- error_data = response.json()
- print(f"错误详情: {error_data.get('detail')}")
-except requests.exceptions.RequestException as e:
- print(f"请求异常: {e}")
-```
-
-**JavaScript**:
-```javascript
-try {
- const response = await fetch(url, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(payload)
- });
-
- if (!response.ok) {
- const error = await response.json();
- throw new Error(error.error || `HTTP ${response.status}`);
- }
-
- const data = await response.json();
-} catch (error) {
- console.error('搜索失败:', error.message);
-}
-```
-
----
-
-### 5. 代码示例
-
-**完整的搜索函数(Python)**:
-
-```python
-import requests
-from typing import Dict, Any, Optional, List
-
-class SearchClient:
- def __init__(self, base_url: str = "http://localhost:6002"):
- self.base_url = base_url
- self.timeout = 10
-
- def search(
- self,
- query: str,
- size: int = 20,
- from_: int = 0,
- filters: Optional[Dict] = None,
- range_filters: Optional[Dict] = None,
- facets: Optional[List] = None,
- sort_by: Optional[str] = None,
- sort_order: str = "desc"
- ) -> Dict[str, Any]:
- """
- 执行搜索查询
-
- Args:
- query: 搜索查询字符串
- size: 返回结果数量
- from_: 分页偏移量
- filters: 精确匹配过滤器
- range_filters: 范围过滤器
- facets: 分面配置
- sort_by: 排序字段
- sort_order: 排序方向
-
- Returns:
- 搜索结果字典
- """
- url = f"{self.base_url}/search/"
- payload = {
- "query": query,
- "size": size,
- "from": from_,
- }
-
- if filters:
- payload["filters"] = filters
- if range_filters:
- payload["range_filters"] = range_filters
- if facets:
- payload["facets"] = facets
- if sort_by:
- payload["sort_by"] = sort_by
- payload["sort_order"] = sort_order
-
- try:
- response = requests.post(
- url,
- json=payload,
- timeout=self.timeout
- )
- response.raise_for_status()
- return response.json()
- except requests.exceptions.RequestException as e:
- raise Exception(f"搜索请求失败: {e}")
-
-# 使用示例
-client = SearchClient()
-result = client.search(
- query="玩具",
- size=20,
- filters={"category_keyword": "益智玩具"},
- range_filters={"min_price": {"gte": 50, "lte": 200}},
- facets=["category_keyword", "vendor_keyword"],
- sort_by="min_price",
- sort_order="asc"
-)
-
-print(f"找到 {result['total']} 个结果")
-for product in result['results']:
- print(f"{product['title']} - ¥{product['price']}")
-```
-
-**完整的搜索函数(JavaScript)**:
-
-```javascript
-class SearchClient {
- constructor(baseUrl = 'http://localhost:6002') {
- this.baseUrl = baseUrl;
- this.timeout = 10000;
- }
-
- async search({
- query,
- size = 20,
- from = 0,
- filters = null,
- rangeFilters = null,
- facets = null,
- sortBy = null,
- sortOrder = 'desc'
- }) {
- const url = `${this.baseUrl}/search/`;
- const payload = {
- query,
- size,
- from,
- };
-
- if (filters) payload.filters = filters;
- if (rangeFilters) payload.range_filters = rangeFilters;
- if (facets) payload.facets = facets;
- if (sortBy) {
- payload.sort_by = sortBy;
- payload.sort_order = sortOrder;
- }
-
- try {
- const response = await fetch(url, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(payload),
- signal: AbortSignal.timeout(this.timeout)
- });
-
- if (!response.ok) {
- const error = await response.json();
- throw new Error(error.error || `HTTP ${response.status}`);
- }
-
- return await response.json();
- } catch (error) {
- throw new Error(`搜索请求失败: ${error.message}`);
- }
- }
-}
-
-// 使用示例
-const client = new SearchClient();
-const result = await client.search({
- query: '玩具',
- size: 20,
- filters: { category_keyword: '益智玩具' },
- rangeFilters: { min_price: { gte: 50, lte: 200 } },
- facets: ['category_keyword', 'vendor_keyword'],
- sortBy: 'min_price',
- sortOrder: 'asc'
-});
-
-console.log(`找到 ${result.total} 个结果`);
-result.results.forEach(product => {
- console.log(`${product.title} - ¥${product.price}`);
-});
-```
-
----
-
-## 附录
-
-### 常用字段列表
-
-#### 过滤字段(使用 `*_keyword` 后缀)
-
-- `category_keyword`: 类目
-- `vendor_keyword`: 品牌/供应商
-- `product_type_keyword`: 商品类型
-- `tags_keyword`: 标签
-
-#### 范围字段
-
-- `min_price`: 最低价格
-- `max_price`: 最高价格
-- `compare_at_price`: 原价
-- `create_time`: 创建时间
-- `update_time`: 更新时间
-
-#### 排序字段
-
-- `min_price`: 最低价格
-- `max_price`: 最高价格
-- `title`: 标题(字母序)
-- `create_time`: 创建时间
-- `update_time`: 更新时间
-- `relevance_score`: 相关性分数(默认)
-
diff --git a/API_QUICK_REFERENCE.md b/API_QUICK_REFERENCE.md
deleted file mode 100644
index ba9f8d5..0000000
--- a/API_QUICK_REFERENCE.md
+++ /dev/null
@@ -1,233 +0,0 @@
-# API 快速参考 (v3.0)
-
-## 基础搜索
-
-```bash
-POST /search/
-{
- "query": "芭比娃娃",
- "size": 20
-}
-```
-
----
-
-## 精确匹配过滤
-
-```bash
-{
- "filters": {
- "categoryName_keyword": "玩具", // 单值
- "brandName_keyword": ["乐高", "美泰"] // 多值(OR)
- }
-}
-```
-
----
-
-## 范围过滤
-
-```bash
-{
- "range_filters": {
- "price": {
- "gte": 50, // >=
- "lte": 200 // <=
- }
- }
-}
-```
-
-**操作符**: `gte` (>=), `gt` (>), `lte` (<=), `lt` (<)
-
----
-
-## 分面搜索
-
-### 简单模式
-
-```bash
-{
- "facets": ["categoryName_keyword", "brandName_keyword"]
-}
-```
-
-### 高级模式
-
-```bash
-{
- "facets": [
- {"field": "categoryName_keyword", "size": 15},
- {
- "field": "price",
- "type": "range",
- "ranges": [
- {"key": "0-50", "to": 50},
- {"key": "50-100", "from": 50, "to": 100}
- ]
- }
- ]
-}
-```
-
----
-
-## 排序
-
-```bash
-{
- "sort_by": "min_price",
- "sort_order": "asc" // asc 或 desc
-}
-```
-
----
-
-## 布尔表达式
-
-```bash
-{
- "query": "玩具 AND (乐高 OR 芭比) ANDNOT 电动"
-}
-```
-
-**操作符优先级**: `()` > `ANDNOT` > `AND` > `OR` > `RANK`
-
----
-
-## 分页
-
-```bash
-{
- "size": 20, // 每页数量
- "from": 0 // 偏移量(第1页=0,第2页=20)
-}
-```
-
----
-
-## 完整示例
-
-```bash
-POST /search/
-{
- "query": "玩具",
- "size": 20,
- "from": 0,
- "filters": {
- "categoryName_keyword": ["玩具", "益智玩具"]
- },
- "range_filters": {
- "price": {"gte": 50, "lte": 200}
- },
- "facets": [
- {"field": "brandName_keyword", "size": 15},
- {"field": "categoryName_keyword", "size": 15}
- ],
- "sort_by": "min_price",
- "sort_order": "asc"
-}
-```
-
----
-
-## 响应格式
-
-```json
-{
- "hits": [
- {
- "_id": "12345",
- "_score": 8.5,
- "_source": {...}
- }
- ],
- "total": 118,
- "max_score": 8.5,
- "took_ms": 45,
- "facets": [
- {
- "field": "categoryName_keyword",
- "label": "商品类目",
- "type": "terms",
- "values": [
- {
- "value": "玩具",
- "label": "玩具",
- "count": 85,
- "selected": false
- }
- ]
- }
- ]
-}
-```
-
----
-
-## 其他端点
-
-```bash
-POST /search/image
-{
- "image_url": "https://example.com/image.jpg",
- "size": 20
-}
-
-GET /search/suggestions?q=芭&size=5
-
-GET /search/instant?q=玩具&size=5
-
-GET /search/{doc_id}
-
-GET /admin/health
-GET /admin/config
-GET /admin/stats
-```
-
----
-
-## Python 快速示例
-
-```python
-import requests
-
-result = requests.post('http://localhost:6002/search/', json={
- "query": "玩具",
- "filters": {"categoryName_keyword": "玩具"},
- "range_filters": {"price": {"gte": 50, "lte": 200}},
- "facets": ["brandName_keyword"],
- "sort_by": "min_price",
- "sort_order": "asc"
-}).json()
-
-print(f"找到 {result['total']} 个结果")
-```
-
----
-
-## JavaScript 快速示例
-
-```javascript
-const result = await fetch('http://localhost:6002/search/', {
- method: 'POST',
- headers: {'Content-Type': 'application/json'},
- body: JSON.stringify({
- query: "玩具",
- filters: {categoryName_keyword: "玩具"},
- range_filters: {price: {gte: 50, lte: 200}},
- facets: ["brandName_keyword"],
- sort_by: "min_price",
- sort_order: "asc"
- })
-}).then(r => r.json());
-
-console.log(`找到 ${result.total} 个结果`);
-```
-
----
-
-**详细文档**: [API_DOCUMENTATION.md](API_DOCUMENTATION.md)
-**更多示例**: [API_EXAMPLES.md](API_EXAMPLES.md)
-**在线文档**: http://localhost:6002/docs
-
diff --git a/BEST_PRACTICES_REFACTORING.md b/BEST_PRACTICES_REFACTORING.md
deleted file mode 100644
index 9153cb4..0000000
--- a/BEST_PRACTICES_REFACTORING.md
+++ /dev/null
@@ -1,274 +0,0 @@
-# 最佳实践重构总结
-
-**重构日期**: 2025-11-12
-**重构人员**: AI Assistant
-**重构原则**: 代码简洁、类型安全、单一职责
-
----
-
-## 重构目标
-
-1. **移除兼容代码**:不再支持多种数据格式,统一使用最佳实践
-2. **修复前端422错误**:支持日期时间字符串的 range filter
-3. **保持代码简洁**:单一数据流,清晰的类型定义
-
----
-
-## 修改文件
-
-### 1. `/home/tw/SearchEngine/api/models.py`
-
-#### 修改:RangeFilter 模型
-
-**之前**:只支持数值 (float)
-```python
-gte: Optional[float] = Field(None, description="大于等于 (>=)")
-```
-
-**之后**:支持数值和日期时间字符串
-```python
-gte: Optional[Union[float, str]] = Field(None, description="大于等于 (>=)。数值或ISO日期时间字符串")
-```
-
-**理由**:
-- 价格字段需要数值过滤:`{"gte": 50, "lte": 200}`
-- 时间字段需要字符串过滤:`{"gte": "2023-01-01T00:00:00Z"}`
-- 使用 `Union[float, str]` 同时支持两种类型
-
----
-
-### 2. `/home/tw/SearchEngine/search/es_query_builder.py`
-
-#### 修改:build_facets 方法
-
-**之前**:兼容字典和 Pydantic 模型
-```python
-else:
- if isinstance(config, dict):
- field = config['field']
- facet_type = config.get('type', 'terms')
- ...
- else:
- # Pydantic模型
- field = config.field
- facet_type = config.type
- ...
-```
-
-**之后**:只支持标准格式(str 或 FacetConfig)
-```python
-# 简单模式:只有字段名(字符串)
-if isinstance(config, str):
- field = config
- ...
-
-# 高级模式:FacetConfig 对象
-else:
- field = config.field
- facet_type = config.type
- ...
-```
-
-**理由**:
-- API 层已经定义标准格式:`List[Union[str, FacetConfig]]`
-- 移除字典支持,保持单一数据流
-- 代码更简洁,意图更清晰
-
----
-
-### 3. `/home/tw/SearchEngine/search/searcher.py`
-
-#### 修改:_standardize_facets 方法
-
-**之前**:兼容字典和 Pydantic 模型
-```python
-else:
- field = config.get('field') if isinstance(config, dict) else config.field
- facet_type = config.get('type', 'terms') if isinstance(config, dict) else getattr(config, 'type', 'terms')
-```
-
-**之后**:只支持标准格式
-```python
-else:
- # FacetConfig 对象
- field = config.field
- facet_type = config.type
-```
-
-**理由**:
-- 与 build_facets 保持一致
-- 移除冗余的类型检查
-- 代码更清晰易读
-
----
-
-## 数据流设计
-
-### 标准数据流(最佳实践)
-
-```
-API Request
- ↓
-Pydantic 验证 (SearchRequest)
- ↓ facets: List[Union[str, FacetConfig]]
-Searcher.search()
- ↓
-QueryBuilder.build_facets()
- ↓ 只接受 str 或 FacetConfig
-ES Query (aggs)
- ↓
-ES Response (aggregations)
- ↓
-Searcher._standardize_facets()
- ↓ 只处理 str 或 FacetConfig
-List[FacetResult] (Pydantic 模型)
- ↓
-SearchResponse
- ↓
-API Response (JSON)
-```
-
-### 类型定义
-
-```python
-# API 层
-facets: Optional[List[Union[str, FacetConfig]]] = None
-
-# Query Builder 层
-def build_facets(facet_configs: Optional[List[Union[str, 'FacetConfig']]])
-
-# Searcher 层
-def _standardize_facets(...) -> Optional[List[FacetResult]]
-
-# Response 层
-facets: Optional[List[FacetResult]] = None
-```
-
----
-
-## 测试结果
-
-### ✅ 所有测试通过
-
-| 测试场景 | 状态 | 说明 |
-|---------|------|------|
-| 字符串 facets | ✓ | `["categoryName_keyword"]` |
-| FacetConfig 对象 | ✓ | `{"field": "price", "type": "range", ...}` |
-| 数值 range filter | ✓ | `{"gte": 50, "lte": 200}` |
-| 日期时间 range filter | ✓ | `{"gte": "2023-01-01T00:00:00Z"}` |
-| 混合使用 | ✓ | filters + range_filters + facets |
-
-### ✅ 前端 422 错误已修复
-
-**问题**:前端筛选 listing time 时返回 422 Unprocessable Entity
-
-**原因**:`RangeFilter` 只接受 `float`,不接受日期时间字符串
-
-**解决**:修改 `RangeFilter` 支持 `Union[float, str]`
-
-**验证**:
-```bash
-curl -X POST /search/ \
- -d '{"query": "玩具", "range_filters": {"create_time": {"gte": "2023-01-01T00:00:00Z"}}}'
-# ✓ 200 OK
-```
-
----
-
-## 代码质量
-
-### ✅ 无 Linter 错误
-```bash
-No linter errors found.
-```
-
-### ✅ 类型安全
-- 所有类型明确定义
-- Pydantic 自动验证
-- IDE 类型提示完整
-
-### ✅ 代码简洁
-- 移除所有兼容代码
-- 单一数据格式
-- 清晰的控制流
-
----
-
-## 最佳实践原则
-
-### 1. **单一职责**
-每个方法只处理一种标准格式,不兼容多种输入
-
-### 2. **类型明确**
-使用 Pydantic 模型而不是字典,类型在编译时就确定
-
-### 3. **数据流清晰**
-API → Pydantic → 业务逻辑 → Pydantic → Response
-
-### 4. **早期验证**
-在 API 层就验证数据,不在内部做多重兼容检查
-
-### 5. **代码可维护**
-- 删除冗余代码
-- 保持一致性
-- 易于理解和修改
-
----
-
-## 对比总结
-
-| 方面 | 重构前 | 重构后 |
-|------|-------|--------|
-| **数据格式** | 字典 + Pydantic(兼容) | 只有 Pydantic |
-| **类型检查** | 运行时多重检查 | 编译时类型明确 |
-| **代码行数** | 更多(兼容代码) | 更少(单一逻辑) |
-| **可维护性** | 复杂(多种路径) | 简单(单一路径) |
-| **错误处理** | 隐式容错 | 明确验证 |
-| **RangeFilter** | 只支持数值 | 支持数值+字符串 |
-| **前端兼容** | 422 错误 | 完全兼容 |
-
----
-
-## 后续建议
-
-### 1. 代码审查
-- [x] 移除所有字典兼容代码
-- [x] 统一使用 Pydantic 模型
-- [x] 修复前端 422 错误
-
-### 2. 测试覆盖
-- [x] 字符串 facets
-- [x] FacetConfig 对象
-- [x] 数值 range filter
-- [x] 日期时间 range filter
-- [x] 混合场景
-
-### 3. 文档更新
-- [x] 最佳实践文档
-- [x] 数据流设计
-- [ ] API 文档更新
-
-### 4. 性能优化
-- [ ] 添加请求缓存
-- [ ] 优化 Pydantic 验证
-- [ ] 监控性能指标
-
----
-
-## 结论
-
-本次重构成功实现了以下目标:
-
-✅ **代码简洁**:移除所有兼容代码,保持单一数据流
-✅ **类型安全**:统一使用 Pydantic 模型,编译时类型检查
-✅ **功能完整**:修复前端 422 错误,支持所有筛选场景
-✅ **可维护性**:代码清晰,易于理解和修改
-
-**核心原则**:*不做兼容多种方式的代码,定义一种最佳实践,所有模块都适配这种新方式*
-
----
-
-**版本**: v3.1
-**状态**: ✅ 完成并通过测试
-**下次更新**: 根据业务需求扩展
-
diff --git a/CHANGES.md b/CHANGES.md
deleted file mode 100644
index 6bbb8f6..0000000
--- a/CHANGES.md
+++ /dev/null
@@ -1,611 +0,0 @@
-# 变更日志 (CHANGELOG)
-
----
-
-## v3.0 - API 接口重构 (2024-11-12)
-
-### 重大更新
-
-本版本对搜索 API 进行了全面重构,移除硬编码逻辑,实现了灵活通用的 SaaS 接口设计。
-
-#### ❌ 破坏性变更(不向后兼容)
-
-1. **移除硬编码的 price_ranges 参数**
- - 旧方式:`filters: {"price_ranges": ["0-50", "50-100"]}`
- - 新方式:`range_filters: {"price": {"gte": 50, "lte": 100}}`
-
-2. **移除 aggregations 参数**
- - 旧方式:`aggregations: {"category_stats": {"terms": {...}}}`(ES DSL)
- - 新方式:`facets: [{"field": "categoryName_keyword", "size": 15}]`(简化配置)
-
-3. **响应格式变更**
- - 旧方式:`response.aggregations`(ES 原始格式)
- - 新方式:`response.facets`(标准化格式)
-
-#### ✅ 新增功能
-
-1. **结构化过滤参数**
- - 新增 `range_filters` 参数:支持任意数值字段的范围过滤
- - 支持 `gte`, `gt`, `lte`, `lt` 操作符
- - 示例:
- ```json
- {
- "range_filters": {
- "price": {"gte": 50, "lte": 200},
- "days_since_last_update": {"lte": 30}
- }
- }
- ```
-
-2. **简化的分面配置**
- - 新增 `facets` 参数:替代复杂的 ES DSL
- - 支持简单模式(字符串数组)和高级模式(配置对象)
- - 示例:
- ```json
- {
- "facets": [
- "categoryName_keyword", // 简单模式
- {"field": "brandName_keyword", "size": 15} // 高级模式
- ]
- }
- ```
-
-3. **标准化分面响应**
- - 统一的分面结果格式
- - 包含 `field`, `label`, `type`, `values`
- - `values` 包含 `value`, `label`, `count`, `selected`
- - 示例:
- ```json
- {
- "facets": [
- {
- "field": "categoryName_keyword",
- "label": "商品类目",
- "type": "terms",
- "values": [
- {"value": "玩具", "label": "玩具", "count": 85, "selected": false}
- ]
- }
- ]
- }
- ```
-
-4. **新增搜索建议端点**(框架)
- - `GET /search/suggestions`: 自动补全
- - `GET /search/instant`: 即时搜索
- - 注:暂未实现,仅返回框架响应
-
-#### 🔧 代码改进
-
-1. **后端模型层** (`api/models.py`)
- - 新增 `RangeFilter` 模型
- - 新增 `FacetConfig` 模型
- - 新增 `FacetValue` 和 `FacetResult` 模型
- - 更新 `SearchRequest` 和 `SearchResponse`
-
-2. **查询构建器** (`search/es_query_builder.py`)
- - **完全移除** 硬编码的 `price_ranges` 逻辑(第 205-233 行)
- - 重构 `_build_filters` 方法,支持 `range_filters`
- - **删除** `add_dynamic_aggregations` 方法
- - 新增 `build_facets` 方法
-
-3. **搜索执行层** (`search/searcher.py`)
- - 更新 `search()` 方法签名
- - 新增 `_standardize_facets()` 方法
- - 新增 `_get_field_label()` 方法
- - 更新 `SearchResult` 类
-
-4. **API 路由层** (`api/routes/search.py`)
- - 更新所有搜索端点
- - 新增 `/search/suggestions` 端点
- - 新增 `/search/instant` 端点
-
-5. **前端代码** (`frontend/static/js/app.js`)
- - 更新状态管理,添加 `rangeFilters`
- - 完全重写 `displayAggregations` 为 `displayFacets`
- - 更新过滤器处理逻辑
- - 删除所有硬编码的 `price_ranges`
-
-#### 📚 文档更新
-
-- 新增 `API_DOCUMENTATION.md`:完整的 API 接口文档
-- 更新 `README.md`:添加 v3.0 新功能说明
-- 更新 `USER_GUIDE.md`:更新 API 使用示例
-
-#### 🎯 改进要点
-
-**从特定实现到通用 SaaS**:
-- ❌ 移除:硬编码的价格范围值
-- ❌ 移除:暴露 ES DSL 的聚合接口
-- ❌ 移除:不统一的响应格式
-- ✅ 新增:通用的范围过滤器
-- ✅ 新增:简化的分面配置
-- ✅ 新增:标准化的响应格式
-
-#### 📋 迁移指南
-
-**旧接口** → **新接口**
-
-1. **过滤器迁移**:
- ```json
- // 旧
- {"filters": {"price_ranges": ["50-100"]}}
-
- // 新
- {"range_filters": {"price": {"gte": 50, "lte": 100}}}
- ```
-
-2. **聚合迁移**:
- ```json
- // 旧
- {
- "aggregations": {
- "category_stats": {
- "terms": {"field": "categoryName_keyword", "size": 15}
- }
- }
- }
-
- // 新
- {
- "facets": [
- {"field": "categoryName_keyword", "size": 15}
- ]
- }
- ```
-
-3. **响应解析迁移**:
- ```javascript
- // 旧
- data.aggregations.category_stats.buckets.forEach(bucket => {
- console.log(bucket.key, bucket.doc_count);
- });
-
- // 新
- data.facets.forEach(facet => {
- facet.values.forEach(value => {
- console.log(value.value, value.count);
- });
- });
- ```
-
----
-
-## v2.x - 前端优化更新 (2025-11-11)
-
-## 概述
-基于提供的电商搜索引擎参考图片,对前端界面进行了全面重新设计和优化,采用更现代、简洁的布局风格。
-
----
-
-## 修改的文件
-
-### 1. `/home/tw/SearchEngine/frontend/index.html` ✅ 完全重写
-**更改内容:**
-- 去除旧的搜索示例和复杂布局
-- 添加简洁的顶部标题栏(Product + 商品数量 + Fold按钮)
-- 重新设计搜索栏(更简洁)
-- 添加水平筛选标签区域(Categories, Brand, Supplier)
-- 添加排序工具栏(带上下箭头的排序按钮)
-- 改用网格布局展示商品
-- 添加分页组件
-- 将查询信息改为可折叠的Debug区域
-
-**关键改进:**
-```html
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
----
-
-### 2. `/home/tw/SearchEngine/frontend/static/css/style.css` ✅ 完全重写
-**更改内容:**
-- 去除紫色渐变背景,改为白色简洁背景
-- 重新设计所有组件样式
-- 添加顶部标题栏样式
-- 添加水平筛选标签样式(带hover和active状态)
-- 添加排序按钮样式(带箭头)
-- 重新设计商品卡片样式(网格布局)
-- 添加分页样式
-- 优化响应式设计
-
-**关键样式:**
-```css
-/* 白色背景 */
-body {
- background: #f5f5f5;
-}
-
-/* 筛选标签 */
-.filter-tag {
- padding: 6px 15px;
- background: #f8f8f8;
- border: 1px solid #ddd;
- cursor: pointer;
-}
-
-.filter-tag.active {
- background: #e74c3c;
- color: white;
-}
-
-/* 排序箭头 */
-.sort-arrows {
- display: inline-flex;
- flex-direction: column;
- font-size: 10px;
-}
-
-/* 商品网格 */
-.product-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
- gap: 20px;
-}
-
-/* 商品卡片 */
-.product-card {
- background: white;
- border: 1px solid #e0e0e0;
- border-radius: 8px;
- transition: all 0.3s;
-}
-
-.product-card:hover {
- box-shadow: 0 4px 12px rgba(0,0,0,0.1);
- transform: translateY(-2px);
-}
-```
-
-**代码量对比:**
-- 旧版:433行
-- 新版:450行
-- 变化:+17行(增加了更多功能和响应式样式)
-
----
-
-### 3. `/home/tw/SearchEngine/frontend/static/js/app.js` ✅ 完全重构
-**更改内容:**
-- 添加状态管理对象(统一管理所有状态)
-- 重写搜索函数(支持分页)
-- 重写结果展示函数(商品网格布局)
-- 重写筛选聚合函数(水平标签展示)
-- 添加排序函数(支持字段+方向)
-- 添加分页函数(完整分页导航)
-- 优化代码结构(更模块化)
-
-**关键功能:**
-```javascript
-// 状态管理
-let state = {
- query: '',
- currentPage: 1,
- pageSize: 20,
- totalResults: 0,
- filters: {},
- sortBy: '',
- sortOrder: 'desc',
- aggregations: null
-};
-
-// 排序函数(支持上下箭头)
-function sortByField(field, order) {
- state.sortBy = field;
- state.sortOrder = order;
- performSearch(state.currentPage);
-}
-
-// 分页函数
-function goToPage(page) {
- performSearch(page);
- window.scrollTo({ top: 0, behavior: 'smooth' });
-}
-
-// 商品网格展示
-function displayResults(data) {
- // 生成商品卡片HTML
- data.hits.forEach((hit) => {
- html += `
-
-
...
-
...
-
...
-
...
-
- `;
- });
-}
-
-// 水平筛选标签
-function displayAggregations(aggregations) {
- // 显示为可点击的标签
- html += `
-
- ${key} (${count})
-
- `;
-}
-```
-
-**代码量对比:**
-- 旧版:516行
-- 新版:465行
-- 变化:-51行(代码更简洁,功能更强)
-
----
-
-### 4. `/home/tw/SearchEngine/api/app.py` ✅ 添加静态文件服务
-**更改内容:**
-- 导入 `FileResponse` 和 `StaticFiles`
-- 添加前端HTML服务路由
-- 挂载静态文件目录(CSS, JS)
-- 将原有的 `/` 路由改为 `/api`
-
-**关键代码:**
-```python
-from fastapi.responses import FileResponse
-from fastapi.staticfiles import StaticFiles
-
-# 在文件末尾添加
-frontend_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "frontend")
-if os.path.exists(frontend_path):
- # 服务前端HTML
- @app.get("/")
- async def serve_frontend():
- index_path = os.path.join(frontend_path, "index.html")
- if os.path.exists(index_path):
- return FileResponse(index_path)
-
- # 挂载静态文件
- app.mount("/static", StaticFiles(directory=os.path.join(frontend_path, "static")), name="static")
-```
-
----
-
-## 新增的文件
-
-### 5. `/home/tw/SearchEngine/frontend/README.md` ✅ 新建
-前端详细文档,包含:
-- 优化说明
-- 功能介绍
-- 使用方法
-- 技术特点
-- 浏览器兼容性
-- 未来改进计划
-
-### 6. `/home/tw/SearchEngine/FRONTEND_GUIDE.md` ✅ 新建
-快速上手指南,包含:
-- 优化总结
-- 启动方法
-- 测试步骤
-- 常见问题
-- API接口说明
-- 性能指标
-
-### 7. `/home/tw/SearchEngine/scripts/test_frontend.sh` ✅ 新建
-自动化测试脚本,测试:
-- 健康检查
-- 前端HTML
-- CSS文件
-- JavaScript文件
-- 搜索API
-
-### 8. `/home/tw/SearchEngine/CHANGES.md` ✅ 新建
-本文件,记录所有更改。
-
----
-
-## 功能对比表
-
-| 功能 | 旧版前端 | 新版前端 | 状态 |
-|------|---------|---------|------|
-| 背景颜色 | 紫色渐变 | 白色简洁 | ✅ 优化 |
-| 顶部标题栏 | 大标题+副标题 | Product + 商品数 | ✅ 优化 |
-| 搜索框 | 带多个选项 | 简洁搜索框 | ✅ 优化 |
-| 筛选方式 | 左侧垂直面板 | 顶部水平标签 | ✅ 优化 |
-| 筛选交互 | 复选框 | 可点击标签 | ✅ 优化 |
-| 排序方式 | 下拉选择 | 按钮+箭头 | ✅ 优化 |
-| 商品展示 | 列表布局 | 网格布局 | ✅ 优化 |
-| 商品卡片 | 横向卡片 | 垂直卡片 | ✅ 优化 |
-| 分页功能 | ❌ 无 | ✅ 完整分页 | ✅ 新增 |
-| 响应式设计 | 基础支持 | 完整响应式 | ✅ 优化 |
-| 代码结构 | 混乱 | 模块化 | ✅ 优化 |
-| 状态管理 | 分散 | 统一管理 | ✅ 优化 |
-
----
-
-## 技术改进
-
-### 前端架构
-- ✅ **状态管理**:统一的state对象
-- ✅ **模块化**:功能清晰分离
-- ✅ **代码简化**:去除冗余代码
-- ✅ **性能优化**:减少DOM操作
-
-### UI/UX设计
-- ✅ **视觉一致性**:统一的设计语言
-- ✅ **交互直观**:标签式筛选,箭头排序
-- ✅ **响应迅速**:即时反馈
-- ✅ **移动友好**:完整的响应式支持
-
-### 代码质量
-- ✅ **可维护性**:清晰的结构
-- ✅ **可扩展性**:易于添加新功能
-- ✅ **可读性**:注释完整
-- ✅ **无linter错误**:代码规范
-
----
-
-## 测试步骤
-
-### 1. 启动服务
-```bash
-cd /home/tw/SearchEngine
-bash scripts/start_backend.sh
-```
-
-### 2. 运行测试
-```bash
-bash scripts/test_frontend.sh
-```
-
-### 3. 手动测试
-访问:`http://120.76.41.98:6002/`
-
-测试项目:
-- [ ] 页面正常加载
-- [ ] 搜索功能正常
-- [ ] 筛选标签可点击
-- [ ] 排序箭头可用
-- [ ] 商品网格展示正常
-- [ ] 分页功能正常
-- [ ] 响应式布局正常
-
----
-
-## 兼容性
-
-### 浏览器
-- ✅ Chrome 90+
-- ✅ Firefox 88+
-- ✅ Safari 14+
-- ✅ Edge 90+
-- ✅ 移动浏览器
-
-### 屏幕尺寸
-- ✅ 桌面(1920x1080)
-- ✅ 笔记本(1366x768)
-- ✅ 平板(768x1024)
-- ✅ 手机(375x667)
-
----
-
-## 性能指标
-
-| 指标 | 旧版 | 新版 | 改进 |
-|------|------|------|------|
-| 首屏加载 | ~1.5s | ~0.8s | ⬇️ 47% |
-| JavaScript大小 | 15KB | 13KB | ⬇️ 13% |
-| CSS大小 | 12KB | 11KB | ⬇️ 8% |
-| DOM节点数 | ~350 | ~200 | ⬇️ 43% |
-| 重绘次数 | 高 | 低 | ⬆️ 优化 |
-
----
-
-## 最佳实践应用
-
-### HTML
-- ✅ 语义化标签
-- ✅ 无障碍支持(ARIA)
-- ✅ SEO友好
-
-### CSS
-- ✅ CSS Grid布局
-- ✅ Flexbox布局
-- ✅ CSS变量
-- ✅ 媒体查询(响应式)
-
-### JavaScript
-- ✅ ES6+语法
-- ✅ 事件委托
-- ✅ 防抖/节流(如需要)
-- ✅ 错误处理
-
----
-
-## 下一步优化建议
-
-### 短期(1-2周)
-- [ ] 添加加载骨架屏
-- [ ] 优化图片懒加载
-- [ ] 添加搜索建议(自动完成)
-
-### 中期(1个月)
-- [ ] 添加用户偏好设置
-- [ ] 支持多主题切换
-- [ ] 添加商品收藏功能
-
-### 长期(3个月)
-- [ ] PWA支持(离线访问)
-- [ ] 国际化(多语言)
-- [ ] 性能监控
-
----
-
-## 回滚方案
-
-如需回滚到旧版:
-
-```bash
-cd /home/tw/SearchEngine
-git checkout HEAD~1 frontend/
-# 或从备份恢复
-```
-
----
-
-## 总结
-
-### 完成情况
-- ✅ HTML重构:100%
-- ✅ CSS重写:100%
-- ✅ JavaScript重构:100%
-- ✅ 后端适配:100%
-- ✅ 文档编写:100%
-- ✅ 测试脚本:100%
-
-### 核心成果
-1. **更好的用户体验**:简洁、直观的界面
-2. **更强的功能**:完整的筛选、排序、分页
-3. **更好的代码**:模块化、可维护
-4. **更好的性能**:更快的加载和响应
-
-### 达成目标
-✅ 完全符合参考图片的布局风格
-✅ 实现了所有要求的功能
-✅ 遵循了最佳实践
-✅ 代码质量高,易于维护
-✅ 响应式设计,支持多端
-
----
-
-**优化完成时间**:2025-11-11
-**总耗时**:约2小时
-**状态**:✅ 生产就绪
-
diff --git a/INDEX_FIELDS_DOCUMENTATION.md b/INDEX_FIELDS_DOCUMENTATION.md
deleted file mode 100644
index 3b85168..0000000
--- a/INDEX_FIELDS_DOCUMENTATION.md
+++ /dev/null
@@ -1,223 +0,0 @@
-# 索引字段说明文档
-
-本文档详细说明了 Elasticsearch 索引中所有字段的类型、索引方式、数据来源等信息。
-
-## 索引基本信息
-
-- **索引名称**: `search_products`
-- **索引级别**: SPU级别(商品级别)
-- **数据结构**: SPU文档包含嵌套的variants(SKU)数组
-
-## 字段说明表
-
-### 基础字段
-
-| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | 说明 |
-|-----------|-----------|---------|---------|-----------|-----------|-------------|------|
-| tenant_id | KEYWORD | 是 | 精确匹配 | SPU表 | tenant_id | BIGINT | 租户ID,用于多租户隔离 |
-| product_id | KEYWORD | 是 | 精确匹配 | SPU表 | id | BIGINT | 商品ID(SPU ID) |
-| handle | KEYWORD | 是 | 精确匹配 | SPU表 | handle | VARCHAR(255) | 商品URL handle |
-
-### 文本搜索字段
-
-| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | Boost权重 | 说明 |
-|-----------|-----------|---------|---------|-----------|-----------|-------------|-----------|------|
-| title | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | title | VARCHAR(512) | 3.0 | 商品标题,权重最高 |
-| brief | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | brief | VARCHAR(512) | 1.5 | 商品简介 |
-| description | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | description | TEXT | 1.0 | 商品详细描述 |
-
-### SEO字段
-
-| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | Boost权重 | 是否返回 | 说明 |
-|-----------|-----------|---------|---------|-----------|-----------|-------------|-----------|---------|------|
-| seo_title | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | seo_title | VARCHAR(512) | 2.0 | 否 | SEO标题,用于提升相关性 |
-| seo_description | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | seo_description | TEXT | 1.5 | 否 | SEO描述 |
-| seo_keywords | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | seo_keywords | VARCHAR(1024) | 2.0 | 否 | SEO关键词 |
-
-### 分类和标签字段
-
-| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | Boost权重 | 是否返回 | 说明 |
-|-----------|-----------|---------|---------|-----------|-----------|-------------|-----------|---------|------|
-| vendor | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | vendor | VARCHAR(255) | 1.5 | 是 | 供应商/品牌(文本搜索) |
-| vendor_keyword | KEYWORD | 是 | 精确匹配 | SPU表 | vendor | VARCHAR(255) | - | 否 | 供应商/品牌(精确匹配,用于过滤) |
-| product_type | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | category | VARCHAR(255) | 1.5 | 是 | 商品类型(文本搜索) |
-| product_type_keyword | KEYWORD | 是 | 精确匹配 | SPU表 | category | VARCHAR(255) | - | 否 | 商品类型(精确匹配,用于过滤) |
-| tags | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | tags | VARCHAR(1024) | 1.0 | 是 | 标签(文本搜索) |
-| tags_keyword | KEYWORD | 是 | 精确匹配 | SPU表 | tags | VARCHAR(1024) | - | 否 | 标签(精确匹配,用于过滤) |
-| category | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | category | VARCHAR(255) | 1.5 | 是 | 类目(文本搜索) |
-| category_keyword | KEYWORD | 是 | 精确匹配 | SPU表 | category | VARCHAR(255) | - | 否 | 类目(精确匹配,用于过滤) |
-
-### 价格字段
-
-| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | 说明 |
-|-----------|-----------|---------|---------|-----------|-----------|-------------|------|
-| min_price | FLOAT | 是 | 数值范围 | SKU表(聚合计算) | price | DECIMAL(10,2) | 最低价格(从所有SKU中取最小值) |
-| max_price | FLOAT | 是 | 数值范围 | SKU表(聚合计算) | price | DECIMAL(10,2) | 最高价格(从所有SKU中取最大值) |
-| compare_at_price | FLOAT | 是 | 数值范围 | SKU表(聚合计算) | compare_at_price | DECIMAL(10,2) | 原价(从所有SKU中取最大值) |
-
-**价格计算逻辑**:
-- `min_price`: 取该SPU下所有SKU的price字段的最小值
-- `max_price`: 取该SPU下所有SKU的price字段的最大值
-- `compare_at_price`: 取该SPU下所有SKU的compare_at_price字段的最大值(如果存在)
-
-### 图片字段
-
-| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | 说明 |
-|-----------|-----------|---------|---------|-----------|-----------|-------------|------|
-| image_url | KEYWORD | 否 | 不索引 | SPU表 | image_src | VARCHAR(500) | 商品主图URL,仅用于展示 |
-
-### 文本嵌入字段
-
-| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | 说明 |
-|-----------|-----------|---------|---------|-----------|-----------|-------------|------|
-| title_embedding | TEXT_EMBEDDING | 是 | 向量相似度(dot_product) | 计算生成 | title | VARCHAR(512) | 标题的文本向量(1024维),用于语义搜索 |
-
-**说明**:
-- 向量维度:1024
-- 相似度算法:dot_product(点积)
-- 数据来源:基于title字段通过BGE-M3模型生成
-
-### 时间字段
-
-| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | 是否返回 | 说明 |
-|-----------|-----------|---------|---------|-----------|-----------|-------------|---------|------|
-| create_time | DATE | 是 | 日期范围 | SPU表 | create_time | DATETIME | 是 | 创建时间 |
-| update_time | DATE | 是 | 日期范围 | SPU表 | update_time | DATETIME | 是 | 更新时间 |
-| shoplazza_created_at | DATE | 是 | 日期范围 | SPU表 | shoplazza_created_at | DATETIME | 否 | 店匠系统创建时间 |
-| shoplazza_updated_at | DATE | 是 | 日期范围 | SPU表 | shoplazza_updated_at | DATETIME | 否 | 店匠系统更新时间 |
-
-### 嵌套Variants字段(SKU级别)
-
-| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | 说明 |
-|-----------|-----------|---------|---------|-----------|-----------|-------------|------|
-| variants | JSON (nested) | 是 | 嵌套对象 | SKU表 | - | - | 商品变体数组(嵌套结构) |
-
-#### Variants子字段
-
-| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | 说明 |
-|-----------|-----------|---------|---------|-----------|-----------|-------------|------|
-| variants.variant_id | keyword | 是 | 精确匹配 | SKU表 | id | BIGINT | 变体ID(SKU ID) |
-| variants.title | text | 是 | chinese_ecommerce分析器 | SKU表 | title | VARCHAR(500) | 变体标题 |
-| variants.price | float | 是 | 数值范围 | SKU表 | price | DECIMAL(10,2) | 变体价格 |
-| variants.compare_at_price | float | 是 | 数值范围 | SKU表 | compare_at_price | DECIMAL(10,2) | 变体原价 |
-| variants.sku | keyword | 是 | 精确匹配 | SKU表 | sku | VARCHAR(100) | SKU编码 |
-| variants.stock | long | 是 | 数值范围 | SKU表 | inventory_quantity | INT(11) | 库存数量 |
-| variants.options | object | 是 | 对象 | SKU表 | option1/option2/option3 | VARCHAR(255) | 选项(颜色、尺寸等) |
-
-**Variants结构说明**:
-- `variants` 是一个嵌套对象数组,每个元素代表一个SKU
-- 使用ES的nested类型,支持对嵌套字段进行独立查询和过滤
-- `options` 对象包含 `option1`、`option2`、`option3` 三个字段,分别对应SKU表中的选项值
-
-## 字段类型说明
-
-### ES字段类型映射
-
-| ES字段类型 | Elasticsearch映射 | 用途 |
-|-----------|------------------|------|
-| KEYWORD | keyword | 精确匹配、过滤、聚合、排序 |
-| TEXT | text | 全文检索(支持分词) |
-| FLOAT | float | 浮点数(价格、权重等) |
-| LONG | long | 整数(库存、计数等) |
-| DATE | date | 日期时间 |
-| TEXT_EMBEDDING | dense_vector | 文本向量(1024维) |
-| JSON | object/nested | 嵌套对象 |
-
-### 分析器说明
-
-| 分析器名称 | 语言 | 说明 |
-|-----------|------|------|
-| chinese_ecommerce | 中文 | Ansj中文分词器(电商优化),用于中文文本的分词和搜索 |
-
-## 索引配置
-
-### 索引设置
-
-- **分片数**: 1
-- **副本数**: 0
-- **刷新间隔**: 30秒
-
-### 查询域(Query Domains)
-
-系统定义了多个查询域,用于在不同场景下搜索不同的字段组合:
-
-1. **default(默认索引)**: 搜索所有文本字段
- - 包含字段:title, brief, description, seo_title, seo_description, seo_keywords, vendor, product_type, tags, category
- - Boost: 1.0
-
-2. **title(标题索引)**: 仅搜索标题相关字段
- - 包含字段:title, seo_title
- - Boost: 2.0
-
-3. **vendor(品牌索引)**: 仅搜索品牌字段
- - 包含字段:vendor
- - Boost: 1.5
-
-4. **category(类目索引)**: 仅搜索类目字段
- - 包含字段:category
- - Boost: 1.5
-
-5. **tags(标签索引)**: 搜索标签和SEO关键词
- - 包含字段:tags, seo_keywords
- - Boost: 1.0
-
-## 数据转换规则
-
-### 数据类型转换
-
-1. **BIGINT → KEYWORD**: 数字ID转换为字符串(如 `product_id`, `variant_id`)
-2. **DECIMAL → FLOAT**: 价格字段从DECIMAL转换为FLOAT
-3. **INT → LONG**: 库存数量从INT转换为LONG
-4. **DATETIME → DATE**: 时间字段转换为ISO格式字符串
-
-### 特殊处理
-
-1. **价格聚合**: 从多个SKU的价格中计算min_price、max_price、compare_at_price
-2. **图片URL处理**: 如果image_src不是完整URL,会自动添加协议前缀
-3. **选项合并**: 将SKU表的option1、option2、option3合并为options对象
-
-## 注意事项
-
-1. **多租户隔离**: 所有查询必须包含 `tenant_id` 过滤条件
-2. **嵌套查询**: 查询variants字段时需要使用nested查询语法
-3. **字段命名**: 用于过滤的字段应使用 `*_keyword` 后缀的字段
-4. **向量搜索**: title_embedding字段用于语义搜索,需要配合文本查询使用
-5. **Boost权重**: 不同字段的boost权重影响搜索结果的相关性排序
-
-## 数据来源表结构
-
-### SPU表(shoplazza_product_spu)
-
-主要字段:
-- `id`: BIGINT - 主键ID
-- `tenant_id`: BIGINT - 租户ID
-- `handle`: VARCHAR(255) - URL handle
-- `title`: VARCHAR(512) - 商品标题
-- `brief`: VARCHAR(512) - 商品简介
-- `description`: TEXT - 商品描述
-- `vendor`: VARCHAR(255) - 供应商/品牌
-- `category`: VARCHAR(255) - 类目
-- `tags`: VARCHAR(1024) - 标签
-- `seo_title`: VARCHAR(512) - SEO标题
-- `seo_description`: TEXT - SEO描述
-- `seo_keywords`: VARCHAR(1024) - SEO关键词
-- `image_src`: VARCHAR(500) - 图片URL
-- `create_time`: DATETIME - 创建时间
-- `update_time`: DATETIME - 更新时间
-- `shoplazza_created_at`: DATETIME - 店匠创建时间
-- `shoplazza_updated_at`: DATETIME - 店匠更新时间
-
-### SKU表(shoplazza_product_sku)
-
-主要字段:
-- `id`: BIGINT - 主键ID(对应variant_id)
-- `spu_id`: BIGINT - SPU ID(关联字段)
-- `title`: VARCHAR(500) - 变体标题
-- `price`: DECIMAL(10,2) - 价格
-- `compare_at_price`: DECIMAL(10,2) - 原价
-- `sku`: VARCHAR(100) - SKU编码
-- `inventory_quantity`: INT(11) - 库存数量
-- `option1`: VARCHAR(255) - 选项1
-- `option2`: VARCHAR(255) - 选项2
-- `option3`: VARCHAR(255) - 选项3
-
diff --git a/README.md b/README.md
index fa3060c..1aa5d31 100644
--- a/README.md
+++ b/README.md
@@ -15,12 +15,12 @@
| 步骤 | 去哪里看 | 摘要 |
|------|---------|------|
-| 1. 准备环境 | `环境相关.md` / `USAGE_GUIDE.md` | Conda/依赖、Elasticsearch、MySQL、必需的变量 |
-| 2. 构造测试数据 | `TEST_DATA_GUIDE.md` | Tenant1 Mock、Tenant2 CSV、`mock_data.sh` / `ingest.sh` |
-| 3. 启动与验证 | `USAGE_GUIDE.md` | `run.sh` 一键启动、分步脚本、日志与健康检查 |
-| 4. 理解架构 | `设计文档.md` | 数据流、配置系统、查询/搜索/索引模块 |
-| 5. 接入搜索 API | `API_DOCUMENTATION.md` / `API_INTEGRATION_GUIDE.md` | REST 端点、参数、响应、最佳实践 |
-| 6. 查字段定义 | `INDEX_FIELDS_DOCUMENTATION.md` | `search_products` 映射、字段来源、类型与用途 |
+| 1. 准备环境 | `环境配置说明.md` / `Usage-Guide.md` | Conda/依赖、Elasticsearch、MySQL、必需变量 |
+| 2. 构造测试数据 | `测试数据指南.md` | Tenant1 Mock、Tenant2 CSV、`mock_data.sh` / `ingest.sh` |
+| 3. 启动与验证 | `Usage-Guide.md` | `run.sh` 一键启动、分步脚本、日志与健康检查 |
+| 4. 理解架构 | `系统设计文档.md` | 数据流、配置系统、查询/搜索/索引模块 |
+| 5. 接入搜索 API | `搜索API对接指南.md` / `搜索API速查表.md` | REST 端点、参数、响应、最佳实践 |
+| 6. 查字段定义 | `索引字段说明.md` | `search_products` 映射、字段来源、类型与用途 |
> README 仅保留最常用命令的“索引”。细节以主题文档为准。
@@ -52,19 +52,21 @@ curl -X POST http://localhost:6002/search/ \
| 文档 | 内容提要 | 适用场景 |
|------|----------|----------|
-| `环境相关.md` | 系统要求、Conda/依赖、外部服务账号、常用端口 | 首次部署、环境核对 |
-| `USAGE_GUIDE.md` | 环境准备、服务启动、配置、日志、验证手册 | 日常运维、调试 |
-| `TEST_DATA_GUIDE.md` | 两个租户的模拟/CSV数据构造 & MySQL→ES流程 | 数据准备、联调 |
-| `设计文档.md` | 架构、配置系统、索引/查询/排序模块细节 | 研发/扩展功能 |
-| `INDEX_FIELDS_DOCUMENTATION.md` | `search_products` 字段、类型、来源、嵌套结构 | 新增字段、数据对齐 |
-| `API_DOCUMENTATION.md` | REST API(搜索/图片/管理)详解、示例、响应格式 | API 使用、测试 |
-| `API_INTEGRATION_GUIDE.md` | 客户对接指引、最佳实践、错误处理、语言说明 | 第三方集成、SDK 开发 |
-| `API_QUICK_REFERENCE.md` | 常用请求体速查表 | 支持团队快速查阅 |
-| `环境相关.md` + `.env` 模板 | 运行依赖账号、端口、密钥对照表 | 交付 & 运维 |
+| `环境配置说明.md` | 系统要求、Conda/依赖、外部服务账号、常用端口 | 首次部署、环境核对 |
+| `Usage-Guide.md` | 环境准备、服务启动、配置、日志、验证手册 | 日常运维、调试 |
+| `基础配置指南.md` | 租户字段、索引域、排序表达式配置流程 | 新租户开通、配置变更 |
+| `测试数据指南.md` | 两个租户的模拟/CSV 数据构造 & MySQL→ES 流程 | 数据准备、联调 |
+| `测试Pipeline说明.md` | 测试流水线、CI 脚本、上下文说明 | 自动化测试、追踪流水线 |
+| `系统设计文档.md` | 架构、配置系统、索引/查询/排序模块细节 | 研发/扩展功能 |
+| `索引字段说明.md` | `search_products` 字段、类型、来源、嵌套结构 | 新增字段、数据对齐 |
+| `搜索API对接指南.md` | REST API(文本/图片/管理)详解、示例、响应格式 | API 使用、测试 |
+| `搜索API速查表.md` | 常用请求体、过滤器、分面速查表 | 支持团队快速查阅 |
+| `Search-API-Examples.md` | Python/JS/cURL 端到端示例 | 客户工程、SDK 参考 |
+| `环境配置说明.md` + `.env` 模板 | 运行依赖账号、端口、密钥对照表 | 交付 & 运维 |
更多补充材料:
-- `TEST_DATA_GUIDE.md`:包含完整工作流脚本示例
+- `测试数据指南.md`:包含完整工作流脚本示例
- `商品数据源入ES配置规范.md`:数据源映射约定
- `MULTILANG_FEATURE.md`:多语言处理细节
@@ -73,12 +75,12 @@ curl -X POST http://localhost:6002/search/ \
- **数据构建 → MySQL → Elasticsearch**
- `scripts/mock_data.sh`:Tenant1 Mock + Tenant2 CSV 一条龙
- `scripts/ingest.sh [recreate]`:驱动 `indexer/` 模块写入 `search_products`
- - 详解:`TEST_DATA_GUIDE.md`
+ - 详解:`测试数据指南.md`
- **搜索服务 & API**
- `api/`(FastAPI)承载 REST API,`search/` + `query/` 负责查询解析与下发
- - API、分页、过滤、Facet、KNN 等:`API_DOCUMENTATION.md`
- - 对接案例与错误码:`API_INTEGRATION_GUIDE.md`
+ - API、分页、过滤、Facet、KNN 等:`搜索API对接指南.md`
+ - 对接案例、示例与错误码:`搜索API对接指南.md`、`Search-API-Examples.md`
- **配置驱动能力**
- `config/schema/{tenant_id}/config.yaml`:字段定义、索引域、排序表达式、SPU 聚合
@@ -96,16 +98,3 @@ scripts/ 数据/服务脚本(mock_data, ingest, run 等)
frontend/ 简易调试页面
docs/ 运营及中文资料
```
-
-## 常用参考
-
-- **运行/排障**:`USAGE_GUIDE.md`、`环境相关.md`
-- **功能设计**:`设计文档.md`
-- **字段/数据对齐**:`INDEX_FIELDS_DOCUMENTATION.md`
-- **API 对接**:`API_DOCUMENTATION.md`、`API_INTEGRATION_GUIDE.md`
-- **测试数据**:`TEST_DATA_GUIDE.md`
-
-## 许可证
-
-专有软件 - 保留所有权利
-
diff --git a/TEST_DATA_GUIDE.md b/TEST_DATA_GUIDE.md
deleted file mode 100644
index 2da4375..0000000
--- a/TEST_DATA_GUIDE.md
+++ /dev/null
@@ -1,527 +0,0 @@
-# 测试数据构造指南 - SearchEngine
-
-本文档说明如何构造测试数据,包括两种数据源的准备和导入流程。
-
----
-
-## 快速开始
-
-### 1. 构造 Mock 数据(tenant_id=1 和 tenant_id=2)
-
-```bash
-./scripts/mock_data.sh
-```
-
-功能:自动生成 tenant_id=1 的Mock数据,并从CSV导入 tenant_id=2 的数据到MySQL
-
----
-
-### 2. 从 MySQL → Elasticsearch
-
-```bash
-# 导入 tenant_id=1 的数据(重建索引)
-./scripts/ingest.sh 1 true
-
-# 导入 tenant_id=2 的数据(重建索引)
-./scripts/ingest.sh 2 true
-```
-
-
-**用法**:`./scripts/ingest.sh [recreate_index]`
-- `tenant_id`: 租户ID(1 或 2)
-- `recreate_index`: 是否重建索引(`true`/`false`,默认:`false`)
-
----
-
-## 完整工作流程
-
-```bash
-# 1. 构造并导入测试数据到MySQL
-./scripts/mock_data.sh
-
-# 2. 导入 tenant_id=1 的数据到ES
-./scripts/ingest.sh 1 true
-
-# 3. 导入 tenant_id=2 的数据到ES
-./scripts/ingest.sh 2 true
-```
-
----
-
-## 目录
-
-1. [数据说明](#数据说明)
-2. [构造Mock数据(tenant_id=1)](#构造mock数据tenant_id1)
-3. [从CSV导入数据(tenant_id=2)](#从csv导入数据tenant_id2)
-4. [从MySQL导入到Elasticsearch](#从mysql导入到elasticsearch)
-5. [完整工作流程](#完整工作流程)
-6. [常见问题](#常见问题)
-
----
-
-## 数据说明
-
-系统支持两种测试数据源:
-
-1. **Tenant ID = 1**: 自动生成的Mock数据(使用 `generate_test_data.py` 生成)
-2. **Tenant ID = 2**: 从CSV文件导入的真实数据(使用 `import_tenant2_csv.py` 导入)
-
-### 数据表结构
-
-系统使用店匠标准表结构:
-
-- **SPU表**: `shoplazza_product_spu` - 商品SPU数据
-- **SKU表**: `shoplazza_product_sku` - 商品SKU数据
-
-表结构详见 `INDEX_FIELDS_DOCUMENTATION.md`。
-
----
-
-## 构造Mock数据(tenant_id=1)
-
-### 使用一键脚本(推荐)
-
-`mock_data.sh` 脚本会自动生成并导入 tenant_id=1 的Mock数据:
-
-```bash
-cd /home/tw/SearchEngine
-./scripts/mock_data.sh
-```
-
-脚本会自动:
-- 生成 1000 个SPU的Mock数据
-- 导入数据到MySQL
-- 自动计算起始ID,避免主键冲突
-
-### 手动分步执行
-
-如果需要自定义参数,可以分步执行:
-
-#### 步骤1: 生成Mock测试数据
-
-```bash
-python scripts/generate_test_data.py \
- --num-spus 1000 \
- --tenant-id "1" \
- --output test_data_tenant1.sql \
- --db-host 120.79.247.228 \
- --db-port 3316 \
- --db-database saas \
- --db-username saas \
- --db-password <密码>
-```
-
-参数说明:
-- `--num-spus`: 生成的SPU数量(默认:1000)
-- `--tenant-id`: 租户ID(默认:1)
-- `--output`: 输出的SQL文件路径
-- `--db-host`, `--db-port`, `--db-database`, `--db-username`, `--db-password`: 数据库连接信息
-
-#### 步骤2: 导入数据到MySQL
-
-```bash
-python scripts/import_test_data.py \
- --db-host 120.79.247.228 \
- --db-port 3316 \
- --db-database saas \
- --db-username saas \
- --db-password <密码> \
- --sql-file test_data_tenant1.sql \
- --tenant-id "1"
-```
-
-参数说明:
-- `--sql-file`: SQL文件路径
-- `--tenant-id`: 租户ID(用于清理旧数据)
-- 其他参数:数据库连接信息
-
-**注意**: 导入会先清理该 tenant_id 的旧数据,再导入新数据。
-
----
-
-## 从CSV导入数据(tenant_id=2)
-
-### 使用一键脚本(推荐)
-
-`mock_data.sh` 脚本会自动从CSV文件导入 tenant_id=2 的数据:
-
-```bash
-cd /home/tw/SearchEngine
-./scripts/mock_data.sh
-```
-
-**前提条件**: 确保CSV文件存在于以下路径:
-```
-data/customer1/goods_with_pic.5years_congku.csv.shuf.1w
-```
-
-如果CSV文件路径不同,需要修改 `scripts/mock_data.sh` 中的 `TENANT2_CSV_FILE` 变量。
-
-### CSV文件格式要求
-
-CSV文件需要包含以下列(列名不区分大小写):
-
-- `skuId` - SKU ID
-- `name` - 商品名称
-- `name_pinyin` - 拼音(可选)
-- `create_time` - 创建时间(格式:YYYY-MM-DD HH:MM:SS)
-- `ruSkuName` - 俄文SKU名称(可选)
-- `enSpuName` - 英文SPU名称(可选)
-- `categoryName` - 类别名称
-- `supplierName` - 供应商名称
-- `brandName` - 品牌名称
-- `file_id` - 文件ID(可选)
-- `days_since_last_update` - 更新天数(可选)
-- `id` - 商品ID(可选)
-- `imageUrl` - 图片URL(可选)
-
-### 手动分步执行
-
-如果需要自定义参数,可以分步执行:
-
-#### 步骤1: 从CSV生成SQL文件
-
-```bash
-python scripts/import_tenant2_csv.py \
- --csv-file data/customer1/goods_with_pic.5years_congku.csv.shuf.1w \
- --tenant-id "2" \
- --output customer1_data.sql \
- --db-host 120.79.247.228 \
- --db-port 3316 \
- --db-database saas \
- --db-username saas \
- --db-password <密码>
-```
-
-参数说明:
-- `--csv-file`: CSV文件路径
-- `--tenant-id`: 租户ID(默认:2)
-- `--output`: 输出的SQL文件路径
-- 其他参数:数据库连接信息
-
-#### 步骤2: 导入数据到MySQL
-
-```bash
-python scripts/import_test_data.py \
- --db-host 120.79.247.228 \
- --db-port 3316 \
- --db-database saas \
- --db-username saas \
- --db-password <密码> \
- --sql-file customer1_data.sql \
- --tenant-id "2"
-```
-
-**注意**:
-- CSV导入会先清理该 tenant_id 的旧数据,再导入新数据
-- 脚本会自动计算起始ID,避免主键冲突
-
----
-
-## 从MySQL导入到Elasticsearch
-
-数据导入到MySQL后,需要使用 `ingest.sh` 脚本将数据从MySQL导入到Elasticsearch。
-
-### 基本用法
-
-```bash
-./scripts/ingest.sh [recreate_index]
-```
-
-参数说明:
-- `tenant_id`: **必需**,租户ID,用于筛选数据库中的数据
-- `recreate_index`: 可选,是否删除并重建索引(true/false,默认:false)
-
-### 使用示例
-
-#### 重建索引并导入数据(推荐首次导入)
-
-```bash
-# 导入tenant_id=1的数据并重建索引
-./scripts/ingest.sh 1 true
-
-# 导入tenant_id=2的数据并重建索引
-./scripts/ingest.sh 2 true
-```
-
-#### 增量导入(不重建索引)
-
-```bash
-# 增量导入tenant_id=1的数据
-./scripts/ingest.sh 1 false
-
-# 增量导入tenant_id=2的数据
-./scripts/ingest.sh 2 false
-```
-
-### 手动执行
-
-如果需要自定义参数,可以手动执行:
-
-```bash
-python scripts/ingest_shoplazza.py \
- --db-host 120.79.247.228 \
- --db-port 3316 \
- --db-database saas \
- --db-username saas \
- --db-password <密码> \
- --tenant-id 1 \
- --es-host http://localhost:9200 \
- --recreate \
- --batch-size 500
-```
-
-参数说明:
-- `--db-host`, `--db-port`, `--db-database`, `--db-username`, `--db-password`: MySQL连接信息
-- `--tenant-id`: 租户ID(必需)
-- `--es-host`: Elasticsearch地址
-- `--recreate`: 是否重建索引
-- `--batch-size`: 批量处理大小(默认:500)
-
-### 检查可用的 tenant_id
-
-如果导入时显示 "No documents to index",脚本会自动显示调试信息,包括:
-- 该 tenant_id 的统计信息(总数、活跃数、已删除数)
-- 数据库中存在的其他 tenant_id 列表
-
-也可以直接查询数据库:
-
-```sql
--- 查看有哪些 tenant_id
-SELECT tenant_id, COUNT(*) as count,
- SUM(CASE WHEN deleted = 0 THEN 1 ELSE 0 END) as active
-FROM shoplazza_product_spu
-GROUP BY tenant_id;
-
--- 检查特定 tenant_id 的数据
-SELECT COUNT(*) FROM shoplazza_product_spu
-WHERE tenant_id = 2 AND deleted = 0;
-```
-
-**注意**:
-- 只有 `deleted=0` 的记录会被导入
-- 首次运行会下载模型文件(BGE-M3和CN-CLIP),大约需要10-30分钟
-- 确保MySQL中存在对应 tenant_id 的数据
-
----
-
-## 完整工作流程
-
-### 完整示例:构造并导入所有测试数据
-
-```bash
-# 1. 构造并导入 tenant_id=1 的Mock数据到MySQL
-./scripts/mock_data.sh
-
-# 脚本会自动完成:
-# - 生成 tenant_id=1 的Mock数据(1000个SPU)
-# - 从CSV导入 tenant_id=2 的数据
-# - 导入数据到MySQL
-
-# 2. 从MySQL导入 tenant_id=1 的数据到ES
-./scripts/ingest.sh 1 true
-
-# 3. 从MySQL导入 tenant_id=2 的数据到ES
-./scripts/ingest.sh 2 true
-
-# 4. 验证数据导入
-curl http://localhost:9200/search_products/_count
-```
-
-### 分步执行示例
-
-如果需要更细粒度的控制,可以分步执行:
-
-```bash
-# ===== Part 1: 构造 tenant_id=1 的Mock数据 =====
-
-# 1.1 生成Mock数据
-python scripts/generate_test_data.py \
- --num-spus 1000 \
- --tenant-id "1" \
- --output test_data_tenant1.sql \
- --db-host 120.79.247.228 \
- --db-port 3316 \
- --db-database saas \
- --db-username saas \
- --db-password <密码>
-
-# 1.2 导入到MySQL
-python scripts/import_test_data.py \
- --db-host 120.79.247.228 \
- --db-port 3316 \
- --db-database saas \
- --db-username saas \
- --db-password <密码> \
- --sql-file test_data_tenant1.sql \
- --tenant-id "1"
-
-# ===== Part 2: 从CSV导入 tenant_id=2 的数据 =====
-
-# 2.1 从CSV生成SQL
-python scripts/import_tenant2_csv.py \
- --csv-file data/customer1/goods_with_pic.5years_congku.csv.shuf.1w \
- --tenant-id "2" \
- --output customer1_data.sql \
- --db-host 120.79.247.228 \
- --db-port 3316 \
- --db-database saas \
- --db-username saas \
- --db-password <密码>
-
-# 2.2 导入到MySQL
-python scripts/import_test_data.py \
- --db-host 120.79.247.228 \
- --db-port 3316 \
- --db-database saas \
- --db-username saas \
- --db-password <密码> \
- --sql-file customer1_data.sql \
- --tenant-id "2"
-
-# ===== Part 3: 从MySQL导入到ES =====
-
-# 3.1 导入 tenant_id=1 的数据到ES
-./scripts/ingest.sh 1 true
-
-# 3.2 导入 tenant_id=2 的数据到ES
-./scripts/ingest.sh 2 true
-
-# ===== Part 4: 验证 =====
-
-# 4.1 检查ES中的数据量
-curl http://localhost:9200/search_products/_count
-
-# 4.2 测试搜索
-curl -X POST http://localhost:6002/search/ \
- -H "Content-Type: application/json" \
- -H "X-Tenant-ID: 1" \
- -d '{"query": "玩具", "size": 10}'
-```
-
----
-
-## 常见问题
-
-### Q1: 数据导入失败
-
-**症状**: `Error during data ingestion`
-
-**解决方案**:
-```bash
-# 检查MySQL数据是否存在
-mysql -h 120.79.247.228 -P 3316 -u saas -p saas -e \
- "SELECT COUNT(*) FROM shoplazza_product_spu WHERE tenant_id=1"
-
-# 检查ES索引是否存在
-curl http://localhost:9200/search_products
-
-# 查看详细错误日志
-python scripts/ingest_shoplazza.py --tenant-id 1 --recreate
-```
-
-### Q2: CSV文件找不到
-
-**症状**: `ERROR: CSV file not found`
-
-**解决方案**:
-```bash
-# 检查CSV文件是否存在
-ls -lh data/customer1/goods_with_pic.5years_congku.csv.shuf.1w
-
-# 如果路径不同,修改 scripts/mock_data.sh 中的 TENANT2_CSV_FILE 变量
-```
-
-### Q3: 导入时没有数据
-
-**症状**: `WARNING: No documents to index` 或 `Transformed 0 SPU documents`
-
-**可能原因**:
-1. 数据库中不存在该 tenant_id 的数据
-2. 数据都被标记为 `deleted=1`
-3. tenant_id 类型不匹配
-
-**解决步骤**:
-
-1. **查看调试信息**: 脚本会自动显示调试信息,包括:
- ```
- DEBUG: tenant_id=1000: total=0, active=0, deleted=0
- DEBUG: Available tenant_ids in shoplazza_product_spu:
- tenant_id=1: total=100, active=100
- tenant_id=2: total=50, active=50
- ```
-
-2. **检查数据库**: 直接查询MySQL确认数据
- ```sql
- -- 查看有哪些 tenant_id
- SELECT tenant_id, COUNT(*) as count,
- SUM(CASE WHEN deleted = 0 THEN 1 ELSE 0 END) as active
- FROM shoplazza_product_spu
- GROUP BY tenant_id;
-
- -- 检查特定 tenant_id 的数据
- SELECT COUNT(*) FROM shoplazza_product_spu
- WHERE tenant_id = 2 AND deleted = 0;
- ```
-
-3. **如果数据库中没有数据,需要先导入数据**:
- - 如果有CSV文件,使用CSV导入脚本
- - 如果没有CSV文件,可以使用mock数据生成脚本
-
-4. **使用正确的 tenant_id**: 根据调试信息显示的可用 tenant_id,使用正确的值重新导入
- ```bash
- ./scripts/ingest.sh 2 true # 使用调试信息中显示的 tenant_id
- ```
-
-### Q4: 模型下载慢或失败
-
-**症状**: 首次运行时模型下载很慢或超时
-
-**解决方案**:
-```bash
-# 跳过embedding快速测试(不推荐,但可以快速验证流程)
-# 注意:这会导致搜索功能不完整
-
-# 或手动下载模型到指定目录
-# TEXT_MODEL_DIR=/data/tw/models/bge-m3
-# IMAGE_MODEL_DIR=/data/tw/models/cn-clip
-```
-
-### Q5: 内存不足
-
-**症状**: `Out of memory`
-
-**解决方案**:
-```bash
-# 减少批量大小
-python scripts/ingest_shoplazza.py \
- --tenant-id 1 \
- --batch-size 200 # 默认500,可以减少到100-200
-```
-
-### Q6: 主键冲突
-
-**症状**: `Duplicate entry` 错误
-
-**解决方案**:
-- Mock数据脚本会自动计算起始ID,避免冲突
-- 如果仍有冲突,可以手动清理旧数据:
- ```sql
- DELETE FROM shoplazza_product_spu WHERE tenant_id = 1;
- DELETE FROM shoplazza_product_sku WHERE tenant_id = 1;
- ```
-
----
-
-## 相关文档
-
-- **使用文档**: `USAGE_GUIDE.md` - 环境、启动、配置、日志查看
-- **字段说明文档**: `INDEX_FIELDS_DOCUMENTATION.md` - 索引字段详细说明
-- **API接口文档**: `API_INTEGRATION_GUIDE.md` - 完整的API对接指南
-- **README**: `README.md` - 项目概述和快速开始
-
----
-
-**文档版本**: v2.0
-**最后更新**: 2024-12
-
diff --git a/USAGE_GUIDE.md b/USAGE_GUIDE.md
deleted file mode 100644
index 8305050..0000000
--- a/USAGE_GUIDE.md
+++ /dev/null
@@ -1,441 +0,0 @@
-# 使用指南 - SearchEngine
-
-本文档提供完整的使用指南,包括环境准备、服务启动、配置说明、日志查看等。
-
-## 目录
-
-1. [环境准备](#环境准备)
-2. [服务启动](#服务启动)
-3. [配置说明](#配置说明)
-4. [查看日志](#查看日志)
-5. [测试验证](#测试验证)
-6. [常见问题](#常见问题)
-
----
-
-## 环境准备
-
-### 系统要求
-
-- **操作系统**: Linux (推荐 CentOS 7+ / Ubuntu 18.04+)
-- **Python**: 3.8+
-- **内存**: 建议 8GB+
-- **磁盘**: 10GB+ (包含模型文件)
-- **Elasticsearch**: 8.x (可通过Docker运行)
-
-### 安装依赖
-
-#### 1. 安装Python依赖
-
-```bash
-cd /home/tw/SearchEngine
-pip install -r requirements.txt
-```
-
-#### 2. 启动Elasticsearch
-
-**方式1: 使用Docker(推荐)**
-
-```bash
-docker run -d \
- --name elasticsearch \
- -p 9200:9200 \
- -e "discovery.type=single-node" \
- -e "ES_JAVA_OPTS=-Xms2g -Xmx2g" \
- elasticsearch:8.11.0
-```
-
-**方式2: 本地安装**
-
-参考 [Elasticsearch官方文档](https://www.elastic.co/guide/en/elasticsearch/reference/8.11/install-elasticsearch.html)
-
-#### 3. 配置环境变量
-
-创建 `.env` 文件:
-
-```bash
-# MySQL配置
-DB_HOST=120.79.247.228
-DB_PORT=3316
-DB_DATABASE=saas
-DB_USERNAME=saas
-DB_PASSWORD=your_password
-
-# Elasticsearch配置
-ES_HOST=http://localhost:9200
-ES_USERNAME=essa
-ES_PASSWORD=4hOaLaf41y2VuI8y
-
-# Redis配置(可选,用于缓存)
-REDIS_HOST=localhost
-REDIS_PORT=6479
-REDIS_PASSWORD=BMfv5aI31kgHWtlx
-
-# DeepL翻译API(可选)
-DEEPL_AUTH_KEY=c9293ab4-ad25-479b-919f-ab4e63b429ed
-
-# API服务配置
-API_HOST=0.0.0.0
-API_PORT=6002
-```
-
----
-
-## 服务启动
-
-### 方式1: 一键启动(推荐)
-
-```bash
-cd /home/tw/SearchEngine
-./run.sh
-```
-
-这个脚本会自动:
-1. 创建日志目录
-2. 启动后端API服务(后台运行)
-3. 启动前端Web界面(后台运行)
-4. 等待服务就绪
-
-启动完成后,访问:
-- **前端界面**: http://localhost:6003
-- **后端API**: http://localhost:6002
-- **API文档**: http://localhost:6002/docs
-
-### 方式2: 分步启动
-
-#### 启动后端服务
-
-```bash
-./scripts/start_backend.sh
-```
-
-后端API会在 http://localhost:6002 启动
-
-#### 启动前端服务
-
-```bash
-./scripts/start_frontend.sh
-```
-
-前端界面会在 http://localhost:6003 启动
-
-### 方式3: 手动启动
-
-#### 启动后端API服务
-
-```bash
-python -m api.app \
- --host 0.0.0.0 \
- --port 6002 \
- --es-host http://localhost:9200 \
- --reload
-```
-
-#### 启动前端服务(可选)
-
-```bash
-# 使用Python简单HTTP服务器
-cd frontend
-python -m http.server 6003
-```
-
-### 停止服务
-
-```bash
-# 停止后端
-kill $(cat logs/backend.pid)
-
-# 停止前端
-kill $(cat logs/frontend.pid)
-
-# 或使用停止脚本
-./scripts/stop.sh
-```
-
-### 服务端口
-
-| 服务 | 端口 | URL |
-|------|------|-----|
-| Elasticsearch | 9200 | http://localhost:9200 |
-| Backend API | 6002 | http://localhost:6002 |
-| Frontend Web | 6003 | http://localhost:6003 |
-| API Docs | 6002 | http://localhost:6002/docs |
-
----
-
-## 配置说明
-
-### 环境配置文件 (.env)
-
-主要配置项说明:
-
-```bash
-# Elasticsearch配置
-ES_HOST=http://localhost:9200
-ES_USERNAME=essa
-ES_PASSWORD=4hOaLaf41y2VuI8y
-
-# MySQL配置
-DB_HOST=120.79.247.228
-DB_PORT=3316
-DB_DATABASE=saas
-DB_USERNAME=saas
-DB_PASSWORD=your_password
-
-# Redis配置(可选,用于缓存)
-REDIS_HOST=localhost
-REDIS_PORT=6479
-REDIS_PASSWORD=BMfv5aI31kgHWtlx
-
-# DeepL翻译API
-DEEPL_AUTH_KEY=c9293ab4-ad25-479b-919f-ab4e63b429ed
-
-# API服务配置
-API_HOST=0.0.0.0
-API_PORT=6002
-```
-
-### 修改配置
-
-1. 编辑 `.env` 文件
-2. 重启相关服务
-
----
-
-## 查看日志
-
-### 日志文件位置
-
-日志文件存储在 `logs/` 目录下:
-
-- `logs/backend.log` - 后端服务日志
-- `logs/frontend.log` - 前端服务日志
-- `logs/search_engine.log` - 应用主日志(按天轮转)
-- `logs/errors.log` - 错误日志(按天轮转)
-
-### 查看实时日志
-
-```bash
-# 查看后端日志
-tail -f logs/backend.log
-
-# 查看前端日志
-tail -f logs/frontend.log
-
-# 查看应用主日志
-tail -f logs/search_engine.log
-
-# 查看错误日志
-tail -f logs/errors.log
-```
-
-### 日志级别
-
-日志级别可以通过环境变量 `LOG_LEVEL` 设置:
-
-```bash
-# 在 .env 文件中设置
-LOG_LEVEL=DEBUG # DEBUG, INFO, WARNING, ERROR, CRITICAL
-```
-
-### 日志轮转
-
-日志文件按天自动轮转,保留30天的历史日志。
-
----
-
-## 测试验证
-
-### 1. 健康检查
-
-```bash
-curl http://localhost:6002/admin/health
-```
-
-**预期响应**:
-```json
-{
- "status": "healthy",
- "elasticsearch": "connected"
-}
-```
-
-### 2. 索引统计
-
-```bash
-curl http://localhost:6002/admin/stats
-```
-
-### 3. 简单搜索测试
-
-```bash
-curl -X POST http://localhost:6002/search/ \
- -H "Content-Type: application/json" \
- -H "X-Tenant-ID: 2" \
- -d '{
- "query": "玩具",
- "size": 10
- }'
-```
-
-或者通过查询参数:
-
-```bash
-curl -X POST "http://localhost:6002/search/?tenant_id=2" \
- -H "Content-Type: application/json" \
- -d '{
- "query": "玩具",
- "size": 10
- }'
-```
-
-### 4. 带过滤器的搜索
-
-```bash
-curl -X POST http://localhost:6002/search/ \
- -H "Content-Type: application/json" \
- -H "X-Tenant-ID: 2" \
- -d '{
- "query": "玩具",
- "size": 10,
- "filters": {
- "categoryName_keyword": ["玩具", "益智玩具"]
- },
- "range_filters": {
- "price": {"gte": 50, "lte": 200}
- }
- }'
-```
-
-### 5. 分面搜索测试
-
-```bash
-curl -X POST http://localhost:6002/search/ \
- -H "Content-Type: application/json" \
- -H "X-Tenant-ID: 2" \
- -d '{
- "query": "玩具",
- "size": 10,
- "facets": [
- {"field": "categoryName_keyword", "size": 15},
- {"field": "brandName_keyword", "size": 15}
- ]
- }'
-```
-
-### 6. 图片搜索测试
-
-```bash
-curl -X POST http://localhost:6002/search/image \
- -H "Content-Type: application/json" \
- -H "X-Tenant-ID: 2" \
- -d '{
- "image_url": "https://oss.essa.cn/example.jpg",
- "size": 10
- }'
-```
-
-### 7. 前端界面测试
-
-访问 http://localhost:6003 或 http://localhost:6002/ 进行可视化测试。
-
-**注意**: 所有搜索接口都需要通过 `X-Tenant-ID` 请求头或 `tenant_id` 查询参数指定租户ID。
-
----
-
-## 常见问题
-
-### Q1: MySQL连接失败
-
-**症状**: `Failed to connect to MySQL`
-
-**解决方案**:
-```bash
-# 检查MySQL服务状态
-mysql -h 120.79.247.228 -P 3316 -u saas -p -e "SELECT 1"
-
-# 检查配置
-cat .env | grep DB_
-```
-
-### Q2: Elasticsearch连接失败
-
-**症状**: `Failed to connect to Elasticsearch`
-
-**解决方案**:
-```bash
-# 检查ES服务状态
-curl http://localhost:9200
-
-# 检查ES版本
-curl http://localhost:9200 | grep version
-
-# 确认配置
-cat .env | grep ES_
-```
-
-### Q3: 服务启动失败
-
-**症状**: `Address already in use` 或端口被占用
-
-**解决方案**:
-```bash
-# 查看占用端口的进程
-lsof -i :6002 # 后端
-lsof -i :6003 # 前端
-lsof -i :9200 # ES
-
-# 杀掉进程
-kill -9
-
-# 或修改端口配置
-```
-
-### Q4: 搜索无结果
-
-**症状**: 搜索返回空结果
-
-**解决方案**:
-```bash
-# 检查ES中是否有数据
-curl http://localhost:9200/search_products/_count
-
-# 检查tenant_id过滤是否正确
-curl -X POST http://localhost:6002/search/ \
- -H "Content-Type: application/json" \
- -H "X-Tenant-ID: 2" \
- -d '{"query": "*", "size": 10, "debug": true}'
-```
-
-### Q5: 前端无法连接后端
-
-**症状**: CORS错误
-
-**解决方案**:
-- 确保后端在 http://localhost:6002 运行
-- 检查浏览器控制台错误信息
-- 检查后端日志中的CORS配置
-
-### Q6: 翻译不工作
-
-**症状**: 翻译返回原文
-
-**解决方案**:
-- 检查DEEPL_AUTH_KEY是否正确
-- 如果没有API key,系统会使用mock模式(返回原文)
-
----
-
-## 相关文档
-
-- **测试数据构造文档**: `TEST_DATA_GUIDE.md` - 如何构造和导入测试数据
-- **API接口文档**: `API_INTEGRATION_GUIDE.md` - 完整的API对接指南
-- **字段说明文档**: `INDEX_FIELDS_DOCUMENTATION.md` - 索引字段详细说明
-- **设计文档**: `设计文档.md` - 系统架构和设计说明
-- **README**: `README.md` - 项目概述和快速开始
-
----
-
-**文档版本**: v2.0
-**最后更新**: 2024-12
-
diff --git a/docs/BASE_CONFIG_GUIDE.md b/docs/BASE_CONFIG_GUIDE.md
deleted file mode 100644
index 013ec98..0000000
--- a/docs/BASE_CONFIG_GUIDE.md
+++ /dev/null
@@ -1,257 +0,0 @@
-# Base Configuration Guide
-
-店匠通用配置(Base Configuration)使用指南
-
-## 概述
-
-Base配置是店匠(Shoplazza)通用配置,适用于所有使用店匠标准表的客户。该配置采用SPU级别的索引结构,所有客户共享同一个Elasticsearch索引(`search_products`),通过`tenant_id`字段实现数据隔离。
-
-## 核心特性
-
-- **SPU级别索引**:每个ES文档代表一个SPU,包含嵌套的variants数组
-- **统一索引**:所有客户共享`search_products`索引
-- **租户隔离**:通过`tenant_id`字段实现数据隔离
-- **配置简化**:配置只包含ES搜索相关配置,不包含MySQL数据源配置
-- **外部友好格式**:API返回格式不包含ES内部字段(`_id`, `_score`, `_source`)
-
-## 配置说明
-
-### 配置文件位置
-
-`config/schema/base/config.yaml`
-
-### 配置内容
-
-Base配置**不包含**以下内容:
-- `mysql_config` - MySQL数据库配置
-- `main_table` - 主表配置
-- `extension_table` - 扩展表配置
-- `source_table` / `source_column` - 字段数据源映射
-
-Base配置**只包含**:
-- ES字段定义(字段类型、分析器、boost等)
-- 查询域(indexes)配置
-- 查询处理配置(query_config)
-- 排序和打分配置(function_score)
-- SPU配置(spu_config)
-
-### 必需字段
-
-- `tenant_id` (KEYWORD, required) - 租户隔离字段
-
-### 主要字段
-
-- `product_id` - 商品ID
-- `title`, `brief`, `description` - 文本搜索字段
-- `seo_title`, `seo_description`, `seo_keywords` - SEO字段
-- `vendor`, `product_type`, `tags`, `category` - 分类和标签字段
-- `min_price`, `max_price`, `compare_at_price` - 价格字段
-- `variants` (nested) - 嵌套变体数组
-
-## 数据导入流程
-
-### 1. 生成测试数据
-
-```bash
-python scripts/generate_test_data.py \
- --num-spus 100 \
- --tenant-id "1" \
- --start-spu-id 1 \
- --start-sku-id 1 \
- --output test_data.sql
-```
-
-### 2. 导入测试数据到MySQL
-
-```bash
-python scripts/import_test_data.py \
- --db-host localhost \
- --db-port 3306 \
- --db-database saas \
- --db-username root \
- --db-password password \
- --sql-file test_data.sql \
- --tenant-id "1"
-```
-
-### 3. 导入数据到Elasticsearch
-
-```bash
-python scripts/ingest_shoplazza.py \
- --db-host localhost \
- --db-port 3306 \
- --db-database saas \
- --db-username root \
- --db-password password \
- --tenant-id "1" \
- --config base \
- --es-host http://localhost:9200 \
- --recreate \
- --batch-size 500
-```
-
-## API使用
-
-### 搜索接口
-
-**端点**: `POST /search/`
-
-**请求头**:
-```
-X-Tenant-ID: 1
-Content-Type: application/json
-```
-
-**请求体**:
-```json
-{
- "query": "耳机",
- "size": 10,
- "from": 0,
- "filters": {
- "category_keyword": "电子产品"
- },
- "facets": ["category_keyword", "vendor_keyword"]
-}
-```
-
-**响应格式**:
-```json
-{
- "results": [
- {
- "product_id": "1",
- "title": "蓝牙耳机 Sony",
- "handle": "product-1",
- "description": "高品质无线蓝牙耳机",
- "vendor": "Sony",
- "product_type": "电子产品",
- "price": 199.99,
- "compare_at_price": 299.99,
- "currency": "USD",
- "image_url": "//cdn.example.com/products/1.jpg",
- "in_stock": true,
- "variants": [
- {
- "variant_id": "1",
- "title": "黑色",
- "price": 199.99,
- "compare_at_price": 299.99,
- "sku": "SKU-1-1",
- "stock": 50,
- "options": {
- "option1": "黑色"
- }
- }
- ],
- "relevance_score": 0.95
- }
- ],
- "total": 10,
- "max_score": 1.0,
- "facets": [
- {
- "field": "category_keyword",
- "label": "category_keyword",
- "type": "terms",
- "values": [
- {
- "value": "电子产品",
- "label": "电子产品",
- "count": 5,
- "selected": false
- }
- ]
- }
- ],
- "suggestions": [],
- "related_searches": [],
- "took_ms": 15,
- "query_info": {}
-}
-```
-
-### 响应格式说明
-
-#### 主要变化
-
-1. **`results`替代`hits`**:返回字段从`hits`改为`results`
-2. **结构化结果**:每个结果包含`product_id`, `title`, `variants`, `relevance_score`等字段
-3. **无ES内部字段**:不包含`_id`, `_score`, `_source`等ES内部字段
-4. **嵌套variants**:每个商品包含variants数组,每个variant包含完整的变体信息
-5. **相关性分数**:`relevance_score`是ES原始分数(不进行归一化)
-
-#### ProductResult字段
-
-- `product_id` - 商品ID
-- `title` - 商品标题
-- `handle` - 商品handle
-- `description` - 商品描述
-- `vendor` - 供应商/品牌
-- `product_type` - 商品类型
-- `tags` - 标签
-- `price` - 最低价格(min_price)
-- `compare_at_price` - 原价
-- `currency` - 货币单位(默认USD)
-- `image_url` - 主图URL
-- `in_stock` - 是否有库存
-- `variants` - 变体列表
-- `relevance_score` - 相关性分数(ES原始分数)
-
-#### VariantResult字段
-
-- `variant_id` - 变体ID
-- `title` - 变体标题
-- `price` - 价格
-- `compare_at_price` - 原价
-- `sku` - SKU编码
-- `stock` - 库存数量
-- `options` - 选项(颜色、尺寸等)
-
-## 测试
-
-### 运行测试脚本
-
-```bash
-python scripts/test_base.py \
- --api-url http://localhost:8000 \
- --tenant-id "1" \
- --test-tenant-2 "2"
-```
-
-### 测试内容
-
-1. **基本搜索**:测试搜索API基本功能
-2. **响应格式验证**:验证返回格式是否符合要求
-3. **Facets聚合**:测试分面搜索功能
-4. **租户隔离**:验证不同租户的数据隔离
-
-## 常见问题
-
-### Q: 为什么配置中没有MySQL相关配置?
-
-A: 数据源配置和数据导入流程是写死的脚本,不在搜索配置中。搜索配置只关注ES搜索相关的内容。
-
-### Q: 如何为新的租户导入数据?
-
-A: 使用`ingest_shoplazza.py`脚本,指定不同的`--tenant-id`参数即可。
-
-### Q: 如何验证租户隔离是否生效?
-
-A: 使用`test_base.py`脚本,指定两个不同的`--tenant-id`,检查搜索结果是否隔离。
-
-### Q: API返回格式中为什么没有`_id`和`_score`?
-
-A: 为了提供外部友好的API格式,我们移除了ES内部字段,使用`product_id`和`relevance_score`替代。
-
-### Q: 如何添加新的搜索字段?
-
-A: 在`config/schema/base/config.yaml`中添加字段定义,然后重新生成索引映射并重新导入数据。
-
-## 注意事项
-
-1. **tenant_id必需**:所有API请求必须提供`tenant_id`(通过请求头`X-Tenant-ID`或查询参数`tenant_id`)
-2. **索引共享**:所有客户共享`search_products`索引,确保`tenant_id`字段正确设置
-3. **数据导入**:数据导入脚本是写死的,不依赖配置中的MySQL设置
-4. **配置分离**:搜索配置和数据源配置完全分离,提高可维护性
-
diff --git a/docs/RequestContext_README.md b/docs/RequestContext_README.md
deleted file mode 100644
index 3af13ec..0000000
--- a/docs/RequestContext_README.md
+++ /dev/null
@@ -1,374 +0,0 @@
-# RequestContext 使用指南
-
-## 概述
-
-`RequestContext` 是一个请求粒度的上下文管理器,用于跟踪和管理搜索请求的整个生命周期。它提供了统一的数据存储、性能监控和日志记录功能。
-
-## 核心功能
-
-### 1. 查询分析结果存储
-- 原始查询、规范化查询、重写查询
-- 检测语言和翻译结果
-- 查询向量(embedding)
-- 布尔查询AST
-
-### 2. 各检索阶段中间结果
-- 解析后的查询对象
-- 布尔查询语法树
-- ES查询DSL
-- ES响应数据
-- 处理后的搜索结果
-
-### 3. 性能监控
-- 自动计时各阶段耗时
-- 计算各阶段耗时占比
-- 识别性能瓶颈
-- 详细的性能摘要日志
-
-### 4. 错误处理和警告
-- 统一的错误信息存储
-- 警告信息收集
-- 完整的上下文错误跟踪
-
-## 支持的搜索阶段
-
-```python
-class RequestContextStage(Enum):
- TOTAL = "total_search" # 总搜索时间
- QUERY_PARSING = "query_parsing" # 查询解析
- BOOLEAN_PARSING = "boolean_parsing" # 布尔查询解析
- QUERY_BUILDING = "query_building" # ES查询构建
- ELASTICSEARCH_SEARCH = "elasticsearch_search" # ES搜索
- RESULT_PROCESSING = "result_processing" # 结果处理
- RERANKING = "reranking" # 重排序
-```
-
-## 基本使用方法
-
-### 1. 创建RequestContext
-
-```python
-from context import create_request_context, RequestContext
-
-# 方式1: 使用工厂函数
-context = create_request_context(reqid="req-001", uid="user-123")
-
-# 方式2: 直接创建
-context = RequestContext(reqid="req-001", uid="user-123")
-
-# 方式3: 作为上下文管理器使用
-with create_request_context("req-002", "user-456") as context:
- # 搜索逻辑
- pass # 自动记录性能摘要
-```
-
-### 2. 阶段计时
-
-```python
-from context import RequestContextStage
-
-# 开始计时
-context.start_stage(RequestContextStage.QUERY_PARSING)
-
-# 执行查询解析逻辑
-# parsed_query = query_parser.parse(query, context=context)
-
-# 结束计时
-duration = context.end_stage(RequestContextStage.QUERY_PARSING)
-print(f"查询解析耗时: {duration:.2f}ms")
-```
-
-### 3. 存储查询分析结果
-
-```python
-context.store_query_analysis(
- original_query="红色连衣裙",
- normalized_query="红色 连衣裙",
- rewritten_query="红色 女 连衣裙",
- detected_language="zh",
- translations={"en": "red dress"},
- query_vector=[0.1, 0.2, 0.3, ...], # 如果有向量
- is_simple_query=True
-)
-```
-
-### 4. 存储中间结果
-
-```python
-# 存储解析后的查询对象
-context.store_intermediate_result('parsed_query', parsed_query)
-
-# 存储ES查询DSL
-context.store_intermediate_result('es_query', es_query_dict)
-
-# 存储ES响应
-context.store_intermediate_result('es_response', es_response)
-
-# 存储处理后的结果
-context.store_intermediate_result('processed_hits', hits)
-```
-
-### 5. 错误处理和警告
-
-```python
-try:
- # 可能出错的操作
- risky_operation()
-except Exception as e:
- context.set_error(e)
-
-# 添加警告信息
-context.add_warning("查询结果较少,建议放宽搜索条件")
-
-# 检查是否有错误
-if context.has_error():
- print(f"搜索出错: {context.metadata['error_info']}")
-```
-
-## 在Searcher中使用
-
-### 1. 自动创建Context(向后兼容)
-
-```python
-searcher = Searcher(config, es_client)
-
-# Searcher会自动创建RequestContext
-result = searcher.search(
- query="无线蓝牙耳机",
- size=10,
- enable_embedding=True
-)
-
-# 结果中包含context信息
-print(result.context.get_summary())
-```
-
-### 2. 手动创建和传递Context
-
-```python
-# 创建自己的context
-context = create_request_context("my-req-001", "user-789")
-
-# 传递给searcher
-result = searcher.search(
- query="运动鞋",
- context=context # 传递自定义context
-)
-
-# 使用context进行详细分析
-summary = context.get_summary()
-print(f"总耗时: {summary['performance']['total_duration_ms']:.1f}ms")
-```
-
-## 性能分析
-
-### 1. 获取性能摘要
-
-```python
-summary = context.get_summary()
-
-# 基本信息
-print(f"请求ID: {summary['request_info']['reqid']}")
-print(f"总耗时: {summary['performance']['total_duration_ms']:.1f}ms")
-
-# 各阶段耗时
-for stage, duration in summary['performance']['stage_timings_ms'].items():
- percentage = summary['performance']['stage_percentages'].get(stage, 0)
- print(f"{stage}: {duration:.1f}ms ({percentage:.1f}%)")
-
-# 查询分析信息
-query_info = summary['query_analysis']
-print(f"原查询: {query_info['original_query']}")
-print(f"重写查询: {query_info['rewritten_query']}")
-print(f"检测语言: {query_info['detected_language']}")
-```
-
-### 2. 识别性能瓶颈
-
-```python
-summary = context.get_summary()
-
-# 找出耗时超过20%的阶段
-bottlenecks = []
-for stage, percentage in summary['performance']['stage_percentages'].items():
- if percentage > 20:
- bottlenecks.append((stage, percentage))
-
-if bottlenecks:
- print("性能瓶颈:")
- for stage, percentage in bottlenecks:
- print(f" - {stage}: {percentage:.1f}%")
-```
-
-### 3. 自动性能日志
-
-RequestContext会在以下时机自动记录详细的性能摘要日志:
-
-- 上下文管理器退出时 (`with context:`)
-- 手动调用 `context.log_performance_summary()`
-- Searcher.search() 完成时
-
-日志格式示例:
-```
-[2024-01-01 10:30:45] [INFO] [request_context] 搜索请求性能摘要 | reqid: req-001 | 总耗时: 272.6ms | 阶段耗时: | - query_parsing: 35.3ms (13.0%) | - elasticsearch_search: 146.0ms (53.6%) | - result_processing: 18.6ms (6.8%) | 查询: '红色连衣裙' -> '红色 女 连衣裙' (zh) | 结果: 156 hits ES查询: 2456 chars
-```
-
-## 线程安全
-
-RequestContext是线程安全的,支持并发请求处理。每个请求使用独立的context实例,互不干扰。
-
-```python
-import threading
-from context import create_request_context
-
-def worker(request_id, query):
- context = create_request_context(request_id)
- # 搜索逻辑
- # context自动跟踪此线程的请求
- pass
-
-# 多线程并发处理
-threads = []
-for i in range(5):
- t = threading.Thread(target=worker, args=(f"req-{i}", f"query-{i}"))
- threads.append(t)
- t.start()
-
-for t in threads:
- t.join()
-```
-
-## 调试支持
-
-### 1. 检查中间结果
-
-```python
-# 获取查询解析结果
-parsed_query = context.get_intermediate_result('parsed_query')
-
-# 获取ES查询DSL
-es_query = context.get_intermediate_result('es_query')
-
-# 获取ES响应
-es_response = context.get_intermediate_result('es_response')
-
-# 获取原始搜索结果
-raw_hits = context.get_intermediate_result('raw_hits')
-
-# 获取最终处理后的结果
-processed_hits = context.get_intermediate_result('processed_hits')
-```
-
-### 2. 错误诊断
-
-```python
-if context.has_error():
- error_info = context.metadata['error_info']
- print(f"错误类型: {error_info['type']}")
- print(f"错误消息: {error_info['message']}")
-
- # 检查是否有警告
- if context.metadata['warnings']:
- print("警告信息:")
- for warning in context.metadata['warnings']:
- print(f" - {warning}")
-```
-
-## 最佳实践
-
-### 1. 统一使用Context
-
-```python
-# 推荐:在整个搜索流程中传递同一个context
-result = searcher.search(query, context=context)
-
-# 不推荐:在各个环节创建不同的context
-```
-
-### 2. 合理设置阶段边界
-
-```python
-# 只在有意义的大阶段之间计时
-context.start_stage(RequestContextStage.QUERY_PARSING)
-# 整个查询解析逻辑
-context.end_stage(RequestContextStage.QUERY_PARSING)
-
-# 避免在细粒度操作间频繁计时
-```
-
-### 3. 及时存储关键数据
-
-```python
-# 在每个阶段完成后及时存储结果
-context.store_intermediate_result('parsed_query', parsed_query)
-context.store_intermediate_result('es_query', es_query)
-
-# 便于后续调试和分析
-```
-
-### 4. 适当使用警告
-
-```python
-# 使用警告记录非致命问题
-if total_hits < 10:
- context.add_warning("搜索结果较少,建议放宽搜索条件")
-
-if query_time > 5.0:
- context.add_warning(f"查询耗时较长: {query_time:.1f}秒")
-```
-
-## 集成示例
-
-### API接口集成
-
-```python
-from flask import Flask, request, jsonify
-from context import create_request_context
-
-app = Flask(__name__)
-
-@app.route('/search')
-def api_search():
- # 从请求中获取参数
- query = request.args.get('q', '')
- uid = request.args.get('uid', 'anonymous')
-
- # 创建context
- context = create_request_context(uid=uid)
-
- try:
- # 执行搜索
- result = searcher.search(query, context=context)
-
- # 返回结果(包含性能信息)
- response = {
- 'results': result.to_dict(),
- 'performance': context.get_summary()['performance']
- }
-
- return jsonify(response)
-
- except Exception as e:
- context.set_error(e)
- context.log_performance_summary()
-
- return jsonify({
- 'error': str(e),
- 'request_id': context.reqid
- }), 500
-```
-
-## 总结
-
-RequestContext提供了一个强大而灵活的框架,用于管理搜索请求的整个生命周期。通过统一的上下文管理、自动性能监控和详细的日志记录,它显著提升了搜索系统的可观测性和调试能力。
-
-主要优势:
-
-1. **统一管理**: 所有请求相关数据集中存储
-2. **自动监控**: 无需手动计时,自动跟踪性能
-3. **详细日志**: 完整的请求生命周期记录
-4. **向后兼容**: 现有代码无需修改即可受益
-5. **线程安全**: 支持高并发场景
-6. **易于调试**: 丰富的中间结果和错误信息
-
-通过合理使用RequestContext,可以构建更加可靠、高性能和易维护的搜索系统。
\ No newline at end of file
diff --git a/docs/Search-API-Examples.md b/docs/Search-API-Examples.md
new file mode 100644
index 0000000..c262fa2
--- /dev/null
+++ b/docs/Search-API-Examples.md
@@ -0,0 +1,1148 @@
+# API 使用示例
+
+本文档提供了搜索引擎 API 的详细使用示例,包括各种常见场景和最佳实践。
+
+---
+
+## 目录
+
+1. [基础搜索](#基础搜索)
+2. [过滤器使用](#过滤器使用)
+3. [分面搜索](#分面搜索)
+4. [排序](#排序)
+5. [图片搜索](#图片搜索)
+6. [布尔表达式](#布尔表达式)
+7. [完整示例](#完整示例)
+
+---
+
+## 基础搜索
+
+### 示例 1:最简单的搜索
+
+```bash
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "芭比娃娃"
+ }'
+```
+
+**响应**:
+```json
+{
+ "hits": [...],
+ "total": 118,
+ "max_score": 8.5,
+ "took_ms": 45,
+ "query_info": {
+ "original_query": "芭比娃娃",
+ "detected_language": "zh",
+ "translations": {"en": "barbie doll"}
+ }
+}
+```
+
+### 示例 2:指定返回数量
+
+```bash
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "玩具",
+ "size": 50
+ }'
+```
+
+### 示例 3:分页查询
+
+```bash
+# 第1页(0-19)
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "玩具",
+ "size": 20,
+ "from": 0
+ }'
+
+# 第2页(20-39)
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "玩具",
+ "size": 20,
+ "from": 20
+ }'
+```
+
+---
+
+## 过滤器使用
+
+### 精确匹配过滤器
+
+#### 示例 1:单值过滤
+
+```bash
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "玩具",
+ "filters": {
+ "categoryName_keyword": "玩具"
+ }
+ }'
+```
+
+#### 示例 2:多值过滤(OR 逻辑)
+
+```bash
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "娃娃",
+ "filters": {
+ "categoryName_keyword": ["玩具", "益智玩具", "儿童玩具"]
+ }
+ }'
+```
+
+说明:匹配类目为"玩具" **或** "益智玩具" **或** "儿童玩具"的商品。
+
+#### 示例 3:多字段过滤(AND 逻辑)
+
+```bash
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "娃娃",
+ "filters": {
+ "categoryName_keyword": "玩具",
+ "brandName_keyword": "美泰"
+ }
+ }'
+```
+
+说明:必须同时满足"类目=玩具" **并且** "品牌=美泰"。
+
+### 范围过滤器
+
+#### 示例 1:价格范围
+
+```bash
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "玩具",
+ "range_filters": {
+ "price": {
+ "gte": 50,
+ "lte": 200
+ }
+ }
+ }'
+```
+
+说明:价格在 50-200 元之间(包含边界)。
+
+#### 示例 2:只设置下限
+
+```bash
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "玩具",
+ "range_filters": {
+ "price": {
+ "gte": 100
+ }
+ }
+ }'
+```
+
+说明:价格 ≥ 100 元。
+
+#### 示例 3:只设置上限
+
+```bash
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "玩具",
+ "range_filters": {
+ "price": {
+ "lt": 50
+ }
+ }
+ }'
+```
+
+说明:价格 < 50 元(不包含50)。
+
+#### 示例 4:多字段范围过滤
+
+```bash
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "玩具",
+ "range_filters": {
+ "price": {
+ "gte": 50,
+ "lte": 200
+ },
+ "days_since_last_update": {
+ "lte": 30
+ }
+ }
+ }'
+```
+
+说明:价格在 50-200 元 **并且** 最近30天内更新过。
+
+### 组合过滤器
+
+```bash
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "玩具",
+ "filters": {
+ "categoryName_keyword": ["玩具", "益智玩具"],
+ "brandName_keyword": "乐高"
+ },
+ "range_filters": {
+ "price": {
+ "gte": 50,
+ "lte": 500
+ }
+ }
+ }'
+```
+
+说明:类目是"玩具"或"益智玩具" **并且** 品牌是"乐高" **并且** 价格在 50-500 元之间。
+
+---
+
+## 分面搜索
+
+### 简单模式
+
+#### 示例 1:基础分面
+
+```bash
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "玩具",
+ "size": 20,
+ "facets": ["categoryName_keyword", "brandName_keyword"]
+ }'
+```
+
+**响应**:
+```json
+{
+ "hits": [...],
+ "total": 118,
+ "facets": [
+ {
+ "field": "categoryName_keyword",
+ "label": "categoryName_keyword",
+ "type": "terms",
+ "values": [
+ {"value": "玩具", "count": 85, "selected": false},
+ {"value": "益智玩具", "count": 33, "selected": false}
+ ]
+ },
+ {
+ "field": "brandName_keyword",
+ "label": "brandName_keyword",
+ "type": "terms",
+ "values": [
+ {"value": "乐高", "count": 42, "selected": false},
+ {"value": "美泰", "count": 28, "selected": false}
+ ]
+ }
+ ]
+}
+```
+
+### 高级模式
+
+#### 示例 1:自定义分面大小
+
+```bash
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "玩具",
+ "facets": [
+ {
+ "field": "categoryName_keyword",
+ "size": 20,
+ "type": "terms"
+ },
+ {
+ "field": "brandName_keyword",
+ "size": 30,
+ "type": "terms"
+ }
+ ]
+ }'
+```
+
+#### 示例 2:范围分面
+
+```bash
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "玩具",
+ "facets": [
+ {
+ "field": "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}
+ ]
+ }
+ ]
+ }'
+```
+
+**响应**:
+```json
+{
+ "facets": [
+ {
+ "field": "price",
+ "label": "price",
+ "type": "range",
+ "values": [
+ {"value": "0-50", "count": 23, "selected": false},
+ {"value": "50-100", "count": 45, "selected": false},
+ {"value": "100-200", "count": 38, "selected": false},
+ {"value": "200+", "count": 12, "selected": false}
+ ]
+ }
+ ]
+}
+```
+
+#### 示例 3:混合分面(Terms + Range)
+
+```bash
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "玩具",
+ "facets": [
+ {"field": "categoryName_keyword", "size": 15},
+ {"field": "brandName_keyword", "size": 15},
+ {
+ "field": "price",
+ "type": "range",
+ "ranges": [
+ {"key": "低价", "to": 50},
+ {"key": "中价", "from": 50, "to": 200},
+ {"key": "高价", "from": 200}
+ ]
+ }
+ ]
+ }'
+```
+
+---
+
+## 排序
+
+### 示例 1:按价格排序(升序)
+
+```bash
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "玩具",
+ "size": 20,
+ "sort_by": "min_price",
+ "sort_order": "asc"
+ }'
+```
+
+### 示例 2:按创建时间排序(降序)
+
+```bash
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "玩具",
+ "size": 20,
+ "sort_by": "create_time",
+ "sort_order": "desc"
+ }'
+```
+
+### 示例 3:排序+过滤
+
+```bash
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "玩具",
+ "filters": {
+ "categoryName_keyword": "益智玩具"
+ },
+ "sort_by": "min_price",
+ "sort_order": "asc"
+ }'
+```
+
+---
+
+## 图片搜索
+
+### 示例 1:基础图片搜索
+
+```bash
+curl -X POST "http://localhost:6002/search/image" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "image_url": "https://example.com/barbie.jpg",
+ "size": 20
+ }'
+```
+
+### 示例 2:图片搜索+过滤器
+
+```bash
+curl -X POST "http://localhost:6002/search/image" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "image_url": "https://example.com/barbie.jpg",
+ "size": 20,
+ "filters": {
+ "categoryName_keyword": "玩具"
+ },
+ "range_filters": {
+ "price": {
+ "lte": 100
+ }
+ }
+ }'
+```
+
+---
+
+## 布尔表达式
+
+### 示例 1:AND 查询
+
+```bash
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "玩具 AND 乐高"
+ }'
+```
+
+说明:必须同时包含"玩具"和"乐高"。
+
+### 示例 2:OR 查询
+
+```bash
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "芭比 OR 娃娃"
+ }'
+```
+
+说明:包含"芭比"或"娃娃"即可。
+
+### 示例 3:ANDNOT 查询(排除)
+
+```bash
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "玩具 ANDNOT 电动"
+ }'
+```
+
+说明:包含"玩具"但不包含"电动"。
+
+### 示例 4:复杂布尔表达式
+
+```bash
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "玩具 AND (乐高 OR 芭比) ANDNOT 电动"
+ }'
+```
+
+说明:必须包含"玩具",并且包含"乐高"或"芭比",但不包含"电动"。
+
+### 示例 5:域查询
+
+```bash
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "brand:乐高"
+ }'
+```
+
+说明:在品牌域中搜索"乐高"。
+
+---
+
+## 完整示例
+
+### Python 完整示例
+
+```python
+#!/usr/bin/env python3
+import requests
+import json
+
+API_URL = "http://localhost:6002/search/"
+
+def search_products(
+ query,
+ size=20,
+ from_=0,
+ filters=None,
+ range_filters=None,
+ facets=None,
+ sort_by=None,
+ sort_order="desc",
+ debug=False
+):
+ """执行搜索查询"""
+ payload = {
+ "query": query,
+ "size": size,
+ "from": from_
+ }
+
+ if filters:
+ payload["filters"] = filters
+ if range_filters:
+ payload["range_filters"] = range_filters
+ if facets:
+ payload["facets"] = facets
+ if sort_by:
+ payload["sort_by"] = sort_by
+ payload["sort_order"] = sort_order
+ if debug:
+ payload["debug"] = debug
+
+ response = requests.post(API_URL, json=payload)
+ response.raise_for_status()
+ return response.json()
+
+
+# 示例 1:简单搜索
+result = search_products("芭比娃娃", size=10)
+print(f"找到 {result['total']} 个结果")
+for hit in result['hits'][:3]:
+ product = hit['_source']
+ print(f" - {product['name']}: ¥{product.get('price', 'N/A')}")
+
+# 示例 2:带过滤和分面的搜索
+result = search_products(
+ query="玩具",
+ size=20,
+ filters={
+ "categoryName_keyword": ["玩具", "益智玩具"]
+ },
+ range_filters={
+ "price": {"gte": 50, "lte": 200}
+ },
+ facets=[
+ {"field": "brandName_keyword", "size": 15},
+ {"field": "categoryName_keyword", "size": 15},
+ {
+ "field": "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}
+ ]
+ }
+ ],
+ sort_by="min_price",
+ sort_order="asc"
+)
+
+# 显示分面结果
+print(f"\n分面统计:")
+for facet in result.get('facets', []):
+ print(f"\n{facet['label']} ({facet['type']}):")
+ for value in facet['values'][:5]:
+ selected_mark = "✓" if value['selected'] else " "
+ print(f" [{selected_mark}] {value['label']}: {value['count']}")
+
+# 示例 3:分页查询
+page = 1
+page_size = 20
+total_pages = 5
+
+for page in range(1, total_pages + 1):
+ result = search_products(
+ query="玩具",
+ size=page_size,
+ from_=(page - 1) * page_size
+ )
+ print(f"\n第 {page} 页:")
+ for hit in result['hits']:
+ product = hit['_source']
+ print(f" - {product['name']}")
+```
+
+### JavaScript 完整示例
+
+```javascript
+// 搜索引擎客户端
+class SearchClient {
+ constructor(baseUrl) {
+ this.baseUrl = baseUrl;
+ }
+
+ async search({
+ query,
+ size = 20,
+ from = 0,
+ filters = null,
+ rangeFilters = null,
+ facets = null,
+ sortBy = null,
+ sortOrder = 'desc',
+ debug = false
+ }) {
+ const payload = {
+ query,
+ size,
+ from
+ };
+
+ if (filters) payload.filters = filters;
+ if (rangeFilters) payload.range_filters = rangeFilters;
+ if (facets) payload.facets = facets;
+ if (sortBy) {
+ payload.sort_by = sortBy;
+ payload.sort_order = sortOrder;
+ }
+ if (debug) payload.debug = debug;
+
+ const response = await fetch(`${this.baseUrl}/search/`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(payload)
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+ }
+
+ return await response.json();
+ }
+
+ async searchByImage(imageUrl, options = {}) {
+ const payload = {
+ image_url: imageUrl,
+ size: options.size || 20,
+ filters: options.filters || null,
+ range_filters: options.rangeFilters || null
+ };
+
+ const response = await fetch(`${this.baseUrl}/search/image`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(payload)
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+ }
+
+ return await response.json();
+ }
+}
+
+// 使用示例
+const client = new SearchClient('http://localhost:6002');
+
+// 简单搜索
+const result1 = await client.search({
+ query: "芭比娃娃",
+ size: 20
+});
+console.log(`找到 ${result1.total} 个结果`);
+
+// 带过滤和分面的搜索
+const result2 = await client.search({
+ query: "玩具",
+ size: 20,
+ filters: {
+ categoryName_keyword: ["玩具", "益智玩具"]
+ },
+ rangeFilters: {
+ price: { gte: 50, lte: 200 }
+ },
+ facets: [
+ { field: "brandName_keyword", size: 15 },
+ { field: "categoryName_keyword", size: 15 }
+ ],
+ sortBy: "price",
+ sortOrder: "asc"
+});
+
+// 显示分面结果
+result2.facets.forEach(facet => {
+ console.log(`\n${facet.label}:`);
+ facet.values.forEach(value => {
+ const selected = value.selected ? '✓' : ' ';
+ console.log(` [${selected}] ${value.label}: ${value.count}`);
+ });
+});
+
+// 显示商品
+result2.hits.forEach(hit => {
+ const product = hit._source;
+ console.log(`${product.name} - ¥${product.price}`);
+});
+```
+
+### 前端完整示例(Vue.js 风格)
+
+```javascript
+// 搜索组件
+const SearchComponent = {
+ data() {
+ return {
+ query: '',
+ results: [],
+ facets: [],
+ filters: {},
+ rangeFilters: {},
+ total: 0,
+ currentPage: 1,
+ pageSize: 20
+ };
+ },
+ methods: {
+ async search() {
+ const response = await fetch('http://localhost:6002/search/', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ query: this.query,
+ size: this.pageSize,
+ from: (this.currentPage - 1) * this.pageSize,
+ filters: this.filters,
+ range_filters: this.rangeFilters,
+ facets: [
+ { field: 'categoryName_keyword', size: 15 },
+ { field: 'brandName_keyword', size: 15 }
+ ]
+ })
+ });
+
+ const data = await response.json();
+ this.results = data.hits;
+ this.facets = data.facets || [];
+ this.total = data.total;
+ },
+
+ toggleFilter(field, value) {
+ if (!this.filters[field]) {
+ this.filters[field] = [];
+ }
+
+ const index = this.filters[field].indexOf(value);
+ if (index > -1) {
+ this.filters[field].splice(index, 1);
+ if (this.filters[field].length === 0) {
+ delete this.filters[field];
+ }
+ } else {
+ this.filters[field].push(value);
+ }
+
+ this.currentPage = 1;
+ this.search();
+ },
+
+ setPriceRange(min, max) {
+ if (min !== null || max !== null) {
+ this.rangeFilters.price = {};
+ if (min !== null) this.rangeFilters.price.gte = min;
+ if (max !== null) this.rangeFilters.price.lte = max;
+ } else {
+ delete this.rangeFilters.price;
+ }
+ this.currentPage = 1;
+ this.search();
+ }
+ }
+};
+```
+
+---
+
+## 调试与优化
+
+### 启用调试模式
+
+```bash
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "玩具",
+ "debug": true
+ }'
+```
+
+**响应包含调试信息**:
+```json
+{
+ "hits": [...],
+ "total": 118,
+ "debug_info": {
+ "query_analysis": {
+ "original_query": "玩具",
+ "normalized_query": "玩具",
+ "rewritten_query": "玩具",
+ "detected_language": "zh",
+ "translations": {"en": "toy"}
+ },
+ "es_query": {
+ "query": {...},
+ "size": 10
+ },
+ "stage_timings": {
+ "query_parsing": 5.3,
+ "elasticsearch_search": 35.1,
+ "result_processing": 4.8
+ }
+ }
+}
+```
+
+### 设置最小分数阈值
+
+```bash
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "玩具",
+ "min_score": 5.0
+ }'
+```
+
+说明:只返回相关性分数 ≥ 5.0 的结果。
+
+---
+
+## 常见使用场景
+
+### 场景 1:电商分类页
+
+```bash
+# 显示某个类目下的所有商品,按价格排序,提供品牌筛选
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "*",
+ "filters": {
+ "categoryName_keyword": "玩具"
+ },
+ "facets": [
+ {"field": "brandName_keyword", "size": 20},
+ {
+ "field": "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}
+ ]
+ }
+ ],
+ "sort_by": "min_price",
+ "sort_order": "asc",
+ "size": 24
+ }'
+```
+
+### 场景 2:搜索结果页
+
+```bash
+# 用户搜索关键词,提供筛选和排序
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "芭比娃娃",
+ "facets": [
+ {"field": "categoryName_keyword", "size": 10},
+ {"field": "brandName_keyword", "size": 10},
+ {"field": "price", "type": "range", "ranges": [
+ {"key": "0-50", "to": 50},
+ {"key": "50-100", "from": 50, "to": 100},
+ {"key": "100+", "from": 100}
+ ]}
+ ],
+ "size": 20
+ }'
+```
+
+### 场景 3:促销专区
+
+```bash
+# 显示特定价格区间的商品
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "*",
+ "range_filters": {
+ "price": {
+ "gte": 50,
+ "lte": 100
+ }
+ },
+ "facets": ["categoryName_keyword", "brandName_keyword"],
+ "sort_by": "min_price",
+ "sort_order": "asc",
+ "size": 50
+ }'
+```
+
+### 场景 4:新品推荐
+
+```bash
+# 最近更新的商品
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "*",
+ "range_filters": {
+ "days_since_last_update": {
+ "lte": 7
+ }
+ },
+ "sort_by": "create_time",
+ "sort_order": "desc",
+ "size": 20
+ }'
+```
+
+---
+
+## 错误处理
+
+### 示例 1:参数错误
+
+```bash
+# 错误:range_filters 缺少操作符
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "玩具",
+ "range_filters": {
+ "price": {}
+ }
+ }'
+```
+
+**响应**:
+```json
+{
+ "error": "Validation error",
+ "detail": "至少需要指定一个范围边界(gte, gt, lte, lt)",
+ "timestamp": 1699800000
+}
+```
+
+### 示例 2:空查询
+
+```bash
+# 错误:query 为空
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": ""
+ }'
+```
+
+**响应**:
+```json
+{
+ "error": "Validation error",
+ "detail": "query field required",
+ "timestamp": 1699800000
+}
+```
+
+---
+
+## 性能优化建议
+
+### 1. 合理使用分面
+
+```bash
+# ❌ 不推荐:请求太多分面
+{
+ "facets": [
+ {"field": "field1", "size": 100},
+ {"field": "field2", "size": 100},
+ {"field": "field3", "size": 100},
+ // ... 10+ facets
+ ]
+}
+
+# ✅ 推荐:只请求必要的分面
+{
+ "facets": [
+ {"field": "categoryName_keyword", "size": 15},
+ {"field": "brandName_keyword", "size": 15}
+ ]
+}
+```
+
+### 2. 控制返回数量
+
+```bash
+# ❌ 不推荐:一次返回太多
+{
+ "size": 100
+}
+
+# ✅ 推荐:分页查询
+{
+ "size": 20,
+ "from": 0
+}
+```
+
+### 3. 使用适当的过滤器
+
+```bash
+# ✅ 推荐:先过滤后搜索
+{
+ "query": "玩具",
+ "filters": {
+ "categoryName_keyword": "玩具"
+ }
+}
+```
+
+---
+
+## 高级技巧
+
+### 技巧 1:获取所有类目
+
+```bash
+# 使用通配符查询 + 分面
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "*",
+ "size": 0,
+ "facets": [
+ {"field": "categoryName_keyword", "size": 100}
+ ]
+ }'
+```
+
+### 技巧 2:价格分布统计
+
+```bash
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "玩具",
+ "size": 0,
+ "facets": [
+ {
+ "field": "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-500", "from": 200, "to": 500},
+ {"key": "500+", "from": 500}
+ ]
+ }
+ ]
+ }'
+```
+
+### 技巧 3:组合多种查询类型
+
+```bash
+# 布尔表达式 + 过滤器 + 分面 + 排序
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "(玩具 OR 游戏) AND 儿童 ANDNOT 电子",
+ "filters": {
+ "categoryName_keyword": ["玩具", "益智玩具"]
+ },
+ "range_filters": {
+ "price": {"gte": 20, "lte": 100},
+ "days_since_last_update": {"lte": 30}
+ },
+ "facets": [
+ {"field": "brandName_keyword", "size": 20}
+ ],
+ "sort_by": "min_price",
+ "sort_order": "asc",
+ "size": 20
+ }'
+```
+
+---
+
+## 测试数据
+
+如果你需要测试数据,可以使用以下查询:
+
+```bash
+# 测试类目:玩具
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{"query": "玩具", "size": 5}'
+
+# 测试品牌:乐高
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{"query": "brand:乐高", "size": 5}'
+
+# 测试布尔表达式
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{"query": "玩具 AND 乐高", "size": 5}'
+```
+
+---
+
+**文档版本**: 3.0
+**最后更新**: 2024-11-12
+**相关文档**: `API_DOCUMENTATION.md`
+
diff --git a/docs/TestingPipeline_README.md b/docs/TestingPipeline_README.md
deleted file mode 100644
index 06ca2c1..0000000
--- a/docs/TestingPipeline_README.md
+++ /dev/null
@@ -1,459 +0,0 @@
-# 搜索引擎测试流水线指南
-
-## 概述
-
-本文档介绍了搜索引擎项目的完整测试流水线,包括测试环境搭建、测试执行、结果分析等内容。测试流水线设计用于commit前的自动化质量保证。
-
-## 🏗️ 测试架构
-
-### 测试层次
-
-```
-测试流水线
-├── 代码质量检查 (Code Quality)
-│ ├── 代码格式化检查 (Black, isort)
-│ ├── 静态分析 (Flake8, MyPy, Pylint)
-│ └── 安全扫描 (Safety, Bandit)
-│
-├── 单元测试 (Unit Tests)
-│ ├── RequestContext测试
-│ ├── Searcher测试
-│ ├── QueryParser测试
-│ └── BooleanParser测试
-│
-├── 集成测试 (Integration Tests)
-│ ├── 端到端搜索流程测试
-│ ├── 多组件协同测试
-│ └── 错误处理测试
-│
-├── API测试 (API Tests)
-│ ├── REST API接口测试
-│ ├── 参数验证测试
-│ ├── 并发请求测试
-│ └── 错误响应测试
-│
-└── 性能测试 (Performance Tests)
- ├── 响应时间测试
- ├── 并发性能测试
- └── 资源使用测试
-```
-
-### 核心组件
-
-1. **RequestContext**: 请求级别的上下文管理器,用于跟踪测试过程中的所有数据
-2. **测试环境管理**: 自动化启动/停止测试依赖服务
-3. **测试执行引擎**: 统一的测试运行和结果收集
-4. **报告生成系统**: 多格式的测试报告生成
-
-## 🚀 快速开始
-
-### 本地测试环境
-
-1. **启动测试环境**
- ```bash
- # 启动所有必要的测试服务
- ./scripts/start_test_environment.sh
- ```
-
-2. **运行完整测试套件**
- ```bash
- # 运行所有测试
- python scripts/run_tests.py
-
- # 或者使用pytest直接运行
- pytest tests/ -v
- ```
-
-3. **停止测试环境**
- ```bash
- ./scripts/stop_test_environment.sh
- ```
-
-### CI/CD测试
-
-1. **GitHub Actions**
- - Push到主分支自动触发
- - Pull Request自动运行
- - 手动触发支持
-
-2. **测试报告**
- - 自动生成并上传
- - PR评论显示测试摘要
- - 详细报告下载
-
-## 📋 测试类型详解
-
-### 1. 单元测试 (Unit Tests)
-
-**位置**: `tests/unit/`
-
-**目的**: 测试单个函数、类、模块的功能
-
-**覆盖范围**:
-- `test_context.py`: RequestContext功能测试
-- `test_searcher.py`: Searcher核心功能测试
-- `test_query_parser.py`: QueryParser处理逻辑测试
-
-**运行方式**:
-```bash
-# 运行所有单元测试
-pytest tests/unit/ -v
-
-# 运行特定测试
-pytest tests/unit/test_context.py -v
-
-# 生成覆盖率报告
-pytest tests/unit/ --cov=. --cov-report=html
-```
-
-### 2. 集成测试 (Integration Tests)
-
-**位置**: `tests/integration/`
-
-**目的**: 测试多个组件协同工作的功能
-
-**覆盖范围**:
-- `test_search_integration.py`: 完整搜索流程集成
-- 数据库、ES、搜索器集成测试
-- 错误传播和处理测试
-
-**运行方式**:
-```bash
-# 运行集成测试(需要启动测试环境)
-pytest tests/integration/ -v -m "not slow"
-
-# 运行包含慢速测试的集成测试
-pytest tests/integration/ -v
-```
-
-### 3. API测试 (API Tests)
-
-**位置**: `tests/integration/test_api_integration.py`
-
-**目的**: 测试HTTP API接口的功能和性能
-
-**覆盖范围**:
-- 基本搜索API
-- 参数验证
-- 错误处理
-- 并发请求
-- Unicode支持
-
-**运行方式**:
-```bash
-# 运行API测试
-pytest tests/integration/test_api_integration.py -v
-```
-
-### 4. 性能测试 (Performance Tests)
-
-**目的**: 验证系统性能指标
-
-**测试内容**:
-- 搜索响应时间
-- API并发处理能力
-- 资源使用情况
-
-**运行方式**:
-```bash
-# 运行性能测试
-python scripts/run_performance_tests.py
-```
-
-## 🛠️ 环境配置
-
-### 测试环境要求
-
-1. **Python环境**
- ```bash
- # 创建测试环境
- conda create -n searchengine-test python=3.9
- conda activate searchengine-test
-
- # 安装依赖
- pip install -r requirements.txt
- pip install pytest pytest-cov pytest-json-report
- ```
-
-2. **Elasticsearch**
- ```bash
- # 使用Docker启动ES
- docker run -d \
- --name elasticsearch \
- -p 9200:9200 \
- -e "discovery.type=single-node" \
- -e "xpack.security.enabled=false" \
- elasticsearch:8.8.0
- ```
-
-3. **环境变量**
- ```bash
- export ES_HOST="http://localhost:9200"
- export ES_USERNAME="elastic"
- export ES_PASSWORD="changeme"
- export API_HOST="127.0.0.1"
- export API_PORT="6003"
- export TENANT_ID="test_tenant"
- export TESTING_MODE="true"
- ```
-
-### 服务依赖
-
-测试环境需要以下服务:
-
-1. **Elasticsearch** (端口9200)
- - 存储和搜索测试数据
- - 支持中文和英文索引
-
-2. **API服务** (端口6003)
- - FastAPI测试服务
- - 提供搜索接口
-
-3. **测试数据库**
- - 预配置的测试索引
- - 包含测试数据
-
-## 📊 测试报告
-
-### 报告类型
-
-1. **实时控制台输出**
- - 测试进度显示
- - 失败详情
- - 性能摘要
-
-2. **JSON格式报告**
- ```json
- {
- "timestamp": "2024-01-01T10:00:00",
- "summary": {
- "total_tests": 150,
- "passed": 148,
- "failed": 2,
- "success_rate": 98.7
- },
- "suites": { ... }
- }
- ```
-
-3. **文本格式报告**
- - 人类友好的格式
- - 包含测试摘要和详情
- - 适合PR评论
-
-4. **HTML覆盖率报告**
- - 代码覆盖率可视化
- - 分支和行覆盖率
- - 缺失测试高亮
-
-### 报告位置
-
-```
-test_logs/
-├── unit_test_results.json # 单元测试结果
-├── integration_test_results.json # 集成测试结果
-├── api_test_results.json # API测试结果
-├── test_report_20240101_100000.txt # 文本格式摘要
-├── test_report_20240101_100000.json # JSON格式详情
-└── htmlcov/ # HTML覆盖率报告
-```
-
-## 🔄 CI/CD集成
-
-### GitHub Actions工作流
-
-**触发条件**:
-- Push到主分支
-- Pull Request创建/更新
-- 手动触发
-
-**工作流阶段**:
-
-1. **代码质量检查**
- - 代码格式验证
- - 静态代码分析
- - 安全漏洞扫描
-
-2. **单元测试**
- - 多Python版本矩阵测试
- - 代码覆盖率收集
- - 自动上传到Codecov
-
-3. **集成测试**
- - 服务依赖启动
- - 端到端功能测试
- - 错误处理验证
-
-4. **API测试**
- - 接口功能验证
- - 参数校验测试
- - 并发请求测试
-
-5. **性能测试**
- - 响应时间检查
- - 资源使用监控
- - 性能回归检测
-
-6. **测试报告生成**
- - 结果汇总
- - 报告上传
- - PR评论更新
-
-### 工作流配置
-
-**文件**: `.github/workflows/test.yml`
-
-**关键特性**:
-- 并行执行提高效率
-- 服务容器化隔离
-- 自动清理资源
-- 智能缓存依赖
-
-## 🧪 测试最佳实践
-
-### 1. 测试编写原则
-
-- **独立性**: 每个测试应该独立运行
-- **可重复性**: 测试结果应该一致
-- **快速执行**: 单元测试应该快速完成
-- **清晰命名**: 测试名称应该描述测试内容
-
-### 2. 测试数据管理
-
-```python
-# 使用fixture提供测试数据
-@pytest.fixture
-def sample_tenant_config():
- return TenantConfig(
- tenant_id="test_tenant",
- es_index_name="test_products"
- )
-
-# 使用mock避免外部依赖
-@patch('search.searcher.ESClient')
-def test_search_with_mock_es(mock_es_client, test_searcher):
- mock_es_client.search.return_value = mock_response
- result = test_searcher.search("test query")
- assert result is not None
-```
-
-### 3. RequestContext集成
-
-```python
-def test_with_context(test_searcher):
- context = create_request_context("test-req", "test-user")
-
- result = test_searcher.search("test query", context=context)
-
- # 验证context被正确更新
- assert context.query_analysis.original_query == "test query"
- assert context.get_stage_duration("elasticsearch_search") > 0
-```
-
-### 4. 性能测试指南
-
-```python
-def test_search_performance(client):
- start_time = time.time()
- response = client.get("/search", params={"q": "test query"})
- response_time = (time.time() - start_time) * 1000
-
- assert response.status_code == 200
- assert response_time < 2000 # 2秒内响应
-```
-
-## 🚨 故障排除
-
-### 常见问题
-
-1. **Elasticsearch连接失败**
- ```bash
- # 检查ES状态
- curl http://localhost:9200/_cluster/health
-
- # 重启ES服务
- docker restart elasticsearch
- ```
-
-2. **测试端口冲突**
- ```bash
- # 检查端口占用
- lsof -i :6003
-
- # 修改API端口
- export API_PORT="6004"
- ```
-
-3. **依赖包缺失**
- ```bash
- # 重新安装依赖
- pip install -r requirements.txt
- pip install pytest pytest-cov pytest-json-report
- ```
-
-4. **测试数据问题**
- ```bash
- # 重新创建测试索引
- curl -X DELETE http://localhost:9200/test_products
- ./scripts/start_test_environment.sh
- ```
-
-### 调试技巧
-
-1. **详细日志输出**
- ```bash
- pytest tests/unit/test_context.py -v -s --tb=long
- ```
-
-2. **运行单个测试**
- ```bash
- pytest tests/unit/test_context.py::TestRequestContext::test_create_context -v
- ```
-
-3. **调试模式**
- ```python
- import pdb; pdb.set_trace()
- ```
-
-4. **性能分析**
- ```bash
- pytest --profile tests/
- ```
-
-## 📈 持续改进
-
-### 测试覆盖率目标
-
-- **单元测试**: > 90%
-- **集成测试**: > 80%
-- **API测试**: > 95%
-
-### 性能基准
-
-- **搜索响应时间**: < 2秒
-- **API并发处理**: 100 QPS
-- **系统资源使用**: < 80% CPU, < 4GB RAM
-
-### 质量门禁
-
-- **所有测试必须通过**
-- **代码覆盖率不能下降**
-- **性能不能显著退化**
-- **不能有安全漏洞**
-
-## 📚 相关文档
-
-- [RequestContext使用指南](RequestContext_README.md)
-- [API文档](../api/README.md)
-- [配置指南](../config/README.md)
-- [部署指南](Deployment_README.md)
-
-## 🤝 贡献指南
-
-1. 为新功能编写对应的测试
-2. 确保测试覆盖率不下降
-3. 遵循测试命名约定
-4. 更新相关文档
-5. 运行完整测试套件后提交
-
-通过这套完整的测试流水线,我们可以确保搜索引擎代码的质量、性能和可靠性,为用户提供稳定高效的搜索服务。
\ No newline at end of file
diff --git a/docs/Usage-Guide.md b/docs/Usage-Guide.md
new file mode 100644
index 0000000..8305050
--- /dev/null
+++ b/docs/Usage-Guide.md
@@ -0,0 +1,441 @@
+# 使用指南 - SearchEngine
+
+本文档提供完整的使用指南,包括环境准备、服务启动、配置说明、日志查看等。
+
+## 目录
+
+1. [环境准备](#环境准备)
+2. [服务启动](#服务启动)
+3. [配置说明](#配置说明)
+4. [查看日志](#查看日志)
+5. [测试验证](#测试验证)
+6. [常见问题](#常见问题)
+
+---
+
+## 环境准备
+
+### 系统要求
+
+- **操作系统**: Linux (推荐 CentOS 7+ / Ubuntu 18.04+)
+- **Python**: 3.8+
+- **内存**: 建议 8GB+
+- **磁盘**: 10GB+ (包含模型文件)
+- **Elasticsearch**: 8.x (可通过Docker运行)
+
+### 安装依赖
+
+#### 1. 安装Python依赖
+
+```bash
+cd /home/tw/SearchEngine
+pip install -r requirements.txt
+```
+
+#### 2. 启动Elasticsearch
+
+**方式1: 使用Docker(推荐)**
+
+```bash
+docker run -d \
+ --name elasticsearch \
+ -p 9200:9200 \
+ -e "discovery.type=single-node" \
+ -e "ES_JAVA_OPTS=-Xms2g -Xmx2g" \
+ elasticsearch:8.11.0
+```
+
+**方式2: 本地安装**
+
+参考 [Elasticsearch官方文档](https://www.elastic.co/guide/en/elasticsearch/reference/8.11/install-elasticsearch.html)
+
+#### 3. 配置环境变量
+
+创建 `.env` 文件:
+
+```bash
+# MySQL配置
+DB_HOST=120.79.247.228
+DB_PORT=3316
+DB_DATABASE=saas
+DB_USERNAME=saas
+DB_PASSWORD=your_password
+
+# Elasticsearch配置
+ES_HOST=http://localhost:9200
+ES_USERNAME=essa
+ES_PASSWORD=4hOaLaf41y2VuI8y
+
+# Redis配置(可选,用于缓存)
+REDIS_HOST=localhost
+REDIS_PORT=6479
+REDIS_PASSWORD=BMfv5aI31kgHWtlx
+
+# DeepL翻译API(可选)
+DEEPL_AUTH_KEY=c9293ab4-ad25-479b-919f-ab4e63b429ed
+
+# API服务配置
+API_HOST=0.0.0.0
+API_PORT=6002
+```
+
+---
+
+## 服务启动
+
+### 方式1: 一键启动(推荐)
+
+```bash
+cd /home/tw/SearchEngine
+./run.sh
+```
+
+这个脚本会自动:
+1. 创建日志目录
+2. 启动后端API服务(后台运行)
+3. 启动前端Web界面(后台运行)
+4. 等待服务就绪
+
+启动完成后,访问:
+- **前端界面**: http://localhost:6003
+- **后端API**: http://localhost:6002
+- **API文档**: http://localhost:6002/docs
+
+### 方式2: 分步启动
+
+#### 启动后端服务
+
+```bash
+./scripts/start_backend.sh
+```
+
+后端API会在 http://localhost:6002 启动
+
+#### 启动前端服务
+
+```bash
+./scripts/start_frontend.sh
+```
+
+前端界面会在 http://localhost:6003 启动
+
+### 方式3: 手动启动
+
+#### 启动后端API服务
+
+```bash
+python -m api.app \
+ --host 0.0.0.0 \
+ --port 6002 \
+ --es-host http://localhost:9200 \
+ --reload
+```
+
+#### 启动前端服务(可选)
+
+```bash
+# 使用Python简单HTTP服务器
+cd frontend
+python -m http.server 6003
+```
+
+### 停止服务
+
+```bash
+# 停止后端
+kill $(cat logs/backend.pid)
+
+# 停止前端
+kill $(cat logs/frontend.pid)
+
+# 或使用停止脚本
+./scripts/stop.sh
+```
+
+### 服务端口
+
+| 服务 | 端口 | URL |
+|------|------|-----|
+| Elasticsearch | 9200 | http://localhost:9200 |
+| Backend API | 6002 | http://localhost:6002 |
+| Frontend Web | 6003 | http://localhost:6003 |
+| API Docs | 6002 | http://localhost:6002/docs |
+
+---
+
+## 配置说明
+
+### 环境配置文件 (.env)
+
+主要配置项说明:
+
+```bash
+# Elasticsearch配置
+ES_HOST=http://localhost:9200
+ES_USERNAME=essa
+ES_PASSWORD=4hOaLaf41y2VuI8y
+
+# MySQL配置
+DB_HOST=120.79.247.228
+DB_PORT=3316
+DB_DATABASE=saas
+DB_USERNAME=saas
+DB_PASSWORD=your_password
+
+# Redis配置(可选,用于缓存)
+REDIS_HOST=localhost
+REDIS_PORT=6479
+REDIS_PASSWORD=BMfv5aI31kgHWtlx
+
+# DeepL翻译API
+DEEPL_AUTH_KEY=c9293ab4-ad25-479b-919f-ab4e63b429ed
+
+# API服务配置
+API_HOST=0.0.0.0
+API_PORT=6002
+```
+
+### 修改配置
+
+1. 编辑 `.env` 文件
+2. 重启相关服务
+
+---
+
+## 查看日志
+
+### 日志文件位置
+
+日志文件存储在 `logs/` 目录下:
+
+- `logs/backend.log` - 后端服务日志
+- `logs/frontend.log` - 前端服务日志
+- `logs/search_engine.log` - 应用主日志(按天轮转)
+- `logs/errors.log` - 错误日志(按天轮转)
+
+### 查看实时日志
+
+```bash
+# 查看后端日志
+tail -f logs/backend.log
+
+# 查看前端日志
+tail -f logs/frontend.log
+
+# 查看应用主日志
+tail -f logs/search_engine.log
+
+# 查看错误日志
+tail -f logs/errors.log
+```
+
+### 日志级别
+
+日志级别可以通过环境变量 `LOG_LEVEL` 设置:
+
+```bash
+# 在 .env 文件中设置
+LOG_LEVEL=DEBUG # DEBUG, INFO, WARNING, ERROR, CRITICAL
+```
+
+### 日志轮转
+
+日志文件按天自动轮转,保留30天的历史日志。
+
+---
+
+## 测试验证
+
+### 1. 健康检查
+
+```bash
+curl http://localhost:6002/admin/health
+```
+
+**预期响应**:
+```json
+{
+ "status": "healthy",
+ "elasticsearch": "connected"
+}
+```
+
+### 2. 索引统计
+
+```bash
+curl http://localhost:6002/admin/stats
+```
+
+### 3. 简单搜索测试
+
+```bash
+curl -X POST http://localhost:6002/search/ \
+ -H "Content-Type: application/json" \
+ -H "X-Tenant-ID: 2" \
+ -d '{
+ "query": "玩具",
+ "size": 10
+ }'
+```
+
+或者通过查询参数:
+
+```bash
+curl -X POST "http://localhost:6002/search/?tenant_id=2" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "玩具",
+ "size": 10
+ }'
+```
+
+### 4. 带过滤器的搜索
+
+```bash
+curl -X POST http://localhost:6002/search/ \
+ -H "Content-Type: application/json" \
+ -H "X-Tenant-ID: 2" \
+ -d '{
+ "query": "玩具",
+ "size": 10,
+ "filters": {
+ "categoryName_keyword": ["玩具", "益智玩具"]
+ },
+ "range_filters": {
+ "price": {"gte": 50, "lte": 200}
+ }
+ }'
+```
+
+### 5. 分面搜索测试
+
+```bash
+curl -X POST http://localhost:6002/search/ \
+ -H "Content-Type: application/json" \
+ -H "X-Tenant-ID: 2" \
+ -d '{
+ "query": "玩具",
+ "size": 10,
+ "facets": [
+ {"field": "categoryName_keyword", "size": 15},
+ {"field": "brandName_keyword", "size": 15}
+ ]
+ }'
+```
+
+### 6. 图片搜索测试
+
+```bash
+curl -X POST http://localhost:6002/search/image \
+ -H "Content-Type: application/json" \
+ -H "X-Tenant-ID: 2" \
+ -d '{
+ "image_url": "https://oss.essa.cn/example.jpg",
+ "size": 10
+ }'
+```
+
+### 7. 前端界面测试
+
+访问 http://localhost:6003 或 http://localhost:6002/ 进行可视化测试。
+
+**注意**: 所有搜索接口都需要通过 `X-Tenant-ID` 请求头或 `tenant_id` 查询参数指定租户ID。
+
+---
+
+## 常见问题
+
+### Q1: MySQL连接失败
+
+**症状**: `Failed to connect to MySQL`
+
+**解决方案**:
+```bash
+# 检查MySQL服务状态
+mysql -h 120.79.247.228 -P 3316 -u saas -p -e "SELECT 1"
+
+# 检查配置
+cat .env | grep DB_
+```
+
+### Q2: Elasticsearch连接失败
+
+**症状**: `Failed to connect to Elasticsearch`
+
+**解决方案**:
+```bash
+# 检查ES服务状态
+curl http://localhost:9200
+
+# 检查ES版本
+curl http://localhost:9200 | grep version
+
+# 确认配置
+cat .env | grep ES_
+```
+
+### Q3: 服务启动失败
+
+**症状**: `Address already in use` 或端口被占用
+
+**解决方案**:
+```bash
+# 查看占用端口的进程
+lsof -i :6002 # 后端
+lsof -i :6003 # 前端
+lsof -i :9200 # ES
+
+# 杀掉进程
+kill -9
+
+# 或修改端口配置
+```
+
+### Q4: 搜索无结果
+
+**症状**: 搜索返回空结果
+
+**解决方案**:
+```bash
+# 检查ES中是否有数据
+curl http://localhost:9200/search_products/_count
+
+# 检查tenant_id过滤是否正确
+curl -X POST http://localhost:6002/search/ \
+ -H "Content-Type: application/json" \
+ -H "X-Tenant-ID: 2" \
+ -d '{"query": "*", "size": 10, "debug": true}'
+```
+
+### Q5: 前端无法连接后端
+
+**症状**: CORS错误
+
+**解决方案**:
+- 确保后端在 http://localhost:6002 运行
+- 检查浏览器控制台错误信息
+- 检查后端日志中的CORS配置
+
+### Q6: 翻译不工作
+
+**症状**: 翻译返回原文
+
+**解决方案**:
+- 检查DEEPL_AUTH_KEY是否正确
+- 如果没有API key,系统会使用mock模式(返回原文)
+
+---
+
+## 相关文档
+
+- **测试数据构造文档**: `TEST_DATA_GUIDE.md` - 如何构造和导入测试数据
+- **API接口文档**: `API_INTEGRATION_GUIDE.md` - 完整的API对接指南
+- **字段说明文档**: `INDEX_FIELDS_DOCUMENTATION.md` - 索引字段详细说明
+- **设计文档**: `设计文档.md` - 系统架构和设计说明
+- **README**: `README.md` - 项目概述和快速开始
+
+---
+
+**文档版本**: v2.0
+**最后更新**: 2024-12
+
diff --git a/docs/reference/商品数据源入ES配置规范.md b/docs/reference/商品数据源入ES配置规范.md
new file mode 100644
index 0000000..a503d64
--- /dev/null
+++ b/docs/reference/商品数据源入ES配置规范.md
@@ -0,0 +1,221 @@
+根据您提供的内容,我将其整理为规范的Markdown格式:
+
+# ES索引配置文档
+
+## 1. 全局配置
+
+### 1.1 文本字段相关性设定
+需要修改所有text字段相关性算法-BM25算法的默认参数:
+```json
+"similarity": {
+ "default": {
+ "type": "BM25",
+ "b": "0.0",
+ "k1": "0.0"
+ }
+}
+```
+
+### 1.2 索引分片设定
+- `number_of_replicas`:0/1
+- `number_of_shards`:设置建议 分片数 <= ES集群的总CPU核心个数/ (副本数 + 1)
+
+### 1.3 索引刷新时间设定
+- `refresh_interval`:默认30S,根据客户需要进行调整
+```json
+"refresh_interval": "30s"
+```
+
+## 2. 单个字段配置
+
+| 分析方式 | 字段预处理和ES输入格式要求 | 对应ES mapping配置 | 备注 |
+|---------|--------------------------|-------------------|------|
+| 电商通用分析-中文 | - | ```json { "type": "text", "analyzer": "index_ansj", "search_analyzer": "query_ansj" } ``` | - |
+| 文本-多语言向量化 | 调用"文本向量化"模块得到1024维向量 | ```json { "type": "dense_vector", "dims": 1024, "index": true, "similarity": "dot_product" } ``` | 1. 依赖"文本向量化"模块
2. 如果定期全量,需要对向量化结果做缓存 |
+| 图片-向量化 | 调用"图片向量化"模块得到1024维向量 | ```json { "type": "nested", "properties": { "vector": { "type": "dense_vector", "dims": 1024, "similarity": "dot_product" }, "url": { "type": "text" } } } ``` | 1. 依赖"图片向量化"模块
2. 如果定期全量,需要对向量化结果做缓存 |
+| 关键词 | ES输入格式:list或者单个值 | ```json {"type": "keyword"} ``` | - |
+| 电商通用分析-英文 | - | ```json {"type": "text", "analyzer": "english"} ``` | - |
+| 电商通用分析-阿拉伯文 | - | ```json {"type": "text", "analyzer": "arabic"} ``` | - |
+| 电商通用分析-西班牙文 | - | ```json {"type": "text", "analyzer": "spanish"} ``` | - |
+| 电商通用分析-俄文 | - | ```json {"type": "text", "analyzer": "russian"} ``` | - |
+| 电商通用分析-日文 | - | ```json {"type": "text", "analyzer": "japanese"} ``` | - |
+| 数值-整数 | - | ```json {"type": "long"} ``` | - |
+| 数值-浮点型 | - | ```json {"type": "float"} ``` | - |
+| 分值 | 输入是float,配置处理方式:log, pow, sigmoid等 | TODO:给代码, log | - |
+| 子串 | - | 暂时不支持 | - |
+| ngram匹配或前缀匹配或边缘前缀匹配 | - | 暂时不支持 | 以后根据需要再添加 |
+
+这样整理后,文档结构更加清晰,表格格式规范,便于阅读和理解。
+
+
+参考 opensearch:
+
+数据接口
+文本相关性字段
+向量相关性字段
+3. 模块提取
+文本向量化
+import sys
+import torch
+from sentence_transformers import SentenceTransformer
+import time
+import threading
+from modelscope import snapshot_download
+from transformers import AutoModel
+import os
+from openai import OpenAI
+from config.logging_config import get_app_logger
+
+# Get logger for this module
+logger = get_app_logger(__name__)
+
+class BgeEncoder:
+ _instance = None
+ _lock = threading.Lock()
+
+ def __new__(cls, model_dir='Xorbits/bge-m3'):
+ with cls._lock:
+ if cls._instance is None:
+ cls._instance = super(BgeEncoder, cls).__new__(cls)
+ logger.info("[BgeEncoder] Creating a new instance with model directory: %s", model_dir)
+ cls._instance.model = SentenceTransformer(snapshot_download(model_dir))
+ logger.info("[BgeEncoder] New instance has been created")
+ return cls._instance
+
+ def encode(self, sentences, normalize_embeddings=True, device='cuda'):
+ # Move model to specified device
+ if device == 'gpu':
+ device = 'cuda'
+ self.model = self.model.to(device)
+ embeddings = self.model.encode(sentences, normalize_embeddings=normalize_embeddings, device=device, show_progress_bar=False)
+ return embeddings
+图片向量化
+import sys
+import os
+import io
+import requests
+import torch
+import numpy as np
+from PIL import Image
+import logging
+import threading
+from typing import List, Optional, Union
+from config.logging_config import get_app_logger
+import cn_clip.clip as clip
+from cn_clip.clip import load_from_name
+
+# Get logger for this module
+logger = get_app_logger(__name__)
+
+# DEFAULT_MODEL_NAME = "ViT-L-14-336" # ["ViT-B-16", "ViT-L-14", "ViT-L-14-336", "ViT-H-14", "RN50"]
+DEFAULT_MODEL_NAME = "ViT-H-14"
+MODEL_DOWNLOAD_DIR = "/data/tw/uat/EsSearcher"
+
+class CLIPImageEncoder:
+ """CLIP Image Encoder for generating image embeddings using cn_clip"""
+
+ _instance = None
+ _lock = threading.Lock()
+
+ def __new__(cls, model_name=DEFAULT_MODEL_NAME, device=None):
+ with cls._lock:
+ if cls._instance is None:
+ cls._instance = super(CLIPImageEncoder, cls).__new__(cls)
+ logger.info(f"[CLIPImageEncoder] Creating new instance with model: {model_name}")
+ cls._instance._initialize_model(model_name, device)
+ return cls._instance
+
+ def _initialize_model(self, model_name, device):
+ """Initialize the CLIP model using cn_clip"""
+ try:
+ self.device = device if device else ("cuda" if torch.cuda.is_available() else "cpu")
+ self.model, self.preprocess = load_from_name(model_name, device=self.device, download_root=MODEL_DOWNLOAD_DIR)
+ self.model.eval()
+ self.model_name = model_name
+ logger.info(f"[CLIPImageEncoder] Model {model_name} initialized successfully on device {self.device}")
+
+ except Exception as e:
+ logger.error(f"[CLIPImageEncoder] Failed to initialize model: {str(e)}")
+ raise
+
+ def validate_image(self, image_data: bytes) -> Image.Image:
+ """Validate image data and return PIL Image if valid"""
+ try:
+ image_stream = io.BytesIO(image_data)
+ image = Image.open(image_stream)
+ image.verify()
+ image_stream.seek(0)
+ image = Image.open(image_stream)
+ if image.mode != 'RGB':
+ image = image.convert('RGB')
+ return image
+ except Exception as e:
+ raise ValueError(f"Invalid image data: {str(e)}")
+
+ def download_image(self, url: str, timeout: int = 10) -> bytes:
+ """Download image from URL"""
+ try:
+ if url.startswith(('http://', 'https://')):
+ response = requests.get(url, timeout=timeout)
+ if response.status_code != 200:
+ raise ValueError(f"HTTP {response.status_code}")
+ return response.content
+ else:
+ # Local file path
+ with open(url, 'rb') as f:
+ return f.read()
+ except Exception as e:
+ raise ValueError(f"Failed to download image from {url}: {str(e)}")
+
+ def preprocess_image(self, image: Image.Image, max_size: int = 1024) -> Image.Image:
+ """Preprocess image for CLIP model"""
+ # Resize if too large
+ if max(image.size) > max_size:
+ ratio = max_size / max(image.size)
+ new_size = tuple(int(dim * ratio) for dim in image.size)
+ image = image.resize(new_size, Image.Resampling.LANCZOS)
+ return image
+
+ def encode_text(self, text):
+ """Encode text to embedding vector using cn_clip"""
+ text_data = clip.tokenize([text] if type(text) == str else text).to(self.device)
+ with torch.no_grad():
+ text_features = self.model.encode_text(text_data)
+ text_features /= text_features.norm(dim=-1, keepdim=True)
+ return text_features
+
+ def encode_image(self, image: Image.Image) -> Optional[np.ndarray]:
+ """Encode image to embedding vector using cn_clip"""
+ if not isinstance(image, Image.Image):
+ raise ValueError("CLIPImageEncoder.encode_image Input must be a PIL.Image")
+
+ try:
+ infer_data = self.preprocess(image).unsqueeze(0).to(self.device)
+ with torch.no_grad():
+ image_features = self.model.encode_image(infer_data)
+ image_features /= image_features.norm(dim=-1, keepdim=True)
+ return image_features.cpu().numpy().astype('float32')[0]
+ except Exception as e:
+ logger.error(f"Failed to process image. Reason: {str(e)}")
+ return None
+
+ def encode_image_from_url(self, url: str) -> Optional[np.ndarray]:
+ """Complete pipeline: download, validate, preprocess and encode image from URL"""
+ try:
+ # Download image
+ image_data = self.download_image(url)
+
+ # Validate image
+ image = self.validate_image(image_data)
+
+ # Preprocess image
+ image = self.preprocess_image(image)
+
+ # Encode image
+ embedding = self.encode_image(image)
+
+ return embedding
+
+ except Exception as e:
+ logger.error(f"Error processing image from URL {url}: {str(e)}")
+ return None
\ No newline at end of file
diff --git a/docs/reference/阿里opensearch电商行业.md b/docs/reference/阿里opensearch电商行业.md
new file mode 100644
index 0000000..2e54e03
--- /dev/null
+++ b/docs/reference/阿里opensearch电商行业.md
@@ -0,0 +1,47 @@
+https://help.aliyun.com/zh/open-search/industry-algorithm-edition/e-commerce?spm=a2c4g.11186623.help-menu-29102.d_3_2_1.5a903cfbxOsaHt&scm=20140722.H_99739._.OR_help-T_cn~zh-V_1
+
+
+## 定义应用结构
+示例如下:
+| 字段名称 | 主键 | 字段标签 | 类型 |
+|----------------|------|------------|--------------|
+| title | | 商品标题 | TEXT |
+| text_embedding | | 文本向量 | EMBEDDING |
+| image_embedding | | 图片向量 | EMBEDDING |
+| category_name | | 类目名称 | TEXT |
+| image_url | | | LITERAL_ARRAY|
+| description | | 商品描述 | TEXT |
+| brand_name | | 品牌名称 | TEXT |
+| thumbnail_url | | | LITERAL_ARRAY|
+| is_onsale | | | INT |
+| url | | | LITERAL |
+| brand_id | | | LITERAL |
+| series_id | | | LITERAL |
+| sold_num | | 商品销量 | INT |
+| category_id | | | INT |
+| onsale_time | | 上架时间 | INT |
+| price | | | DOUBLE |
+| series_name | | | TEXT |
+| discount_price | | DOUBLE |
+| pid | ● | INT |
+| sale_price | | DOUBLE |
+| act_price | | DOUBLE |
+
+
+## 定义索引结构
+
+| 索引名称 | 索引标签 | 包含字段 | 分析方式 | 使用示例 |
+| --- | --- | --- | --- | --- |
+| default | 默认索引 | category_name, description, brand_name, title, create_by, update_by | 行业 - 电商通用分析 | query=default:“云搜索” |
+| category_name | 类目名称索引 | category_name | 行业 - 电商通用分析 | query=category_name:“云搜索” |
+| category_id | | category_id | 关键字 | query=category_id:“云搜索” |
+| series_name | | series_name | 中文 - 通用分析 | query=series_name:“云搜索” |
+| brand_name | | brand_name | 中文 - 通用分析 | query=brand_name:“云搜索” |
+| id | | id | 关键字 | query=id:“云搜索” |
+| title | 标题索引 | title | 行业 - 电商通用分析 | query=title:“云搜索” |
+| seller_id | | seller_id | 关键字 | query=seller_id:“云搜索” |
+| brand_id | | brand_id | 关键字 | query=brand_id:“云搜索” |
+| series_id | | series_id | 关键字 | query=series_id:“云搜索” |
+
+上面的只是阿里云的opensearch的例子,我们也要有同样的一套配置,这里支持的“字分析方式” 为ES预先支持的 多种分析器,我们要支持的分析方式参考 @商品数据源入ES配置规范.md
+
diff --git a/docs/基础配置指南.md b/docs/基础配置指南.md
new file mode 100644
index 0000000..013ec98
--- /dev/null
+++ b/docs/基础配置指南.md
@@ -0,0 +1,257 @@
+# Base Configuration Guide
+
+店匠通用配置(Base Configuration)使用指南
+
+## 概述
+
+Base配置是店匠(Shoplazza)通用配置,适用于所有使用店匠标准表的客户。该配置采用SPU级别的索引结构,所有客户共享同一个Elasticsearch索引(`search_products`),通过`tenant_id`字段实现数据隔离。
+
+## 核心特性
+
+- **SPU级别索引**:每个ES文档代表一个SPU,包含嵌套的variants数组
+- **统一索引**:所有客户共享`search_products`索引
+- **租户隔离**:通过`tenant_id`字段实现数据隔离
+- **配置简化**:配置只包含ES搜索相关配置,不包含MySQL数据源配置
+- **外部友好格式**:API返回格式不包含ES内部字段(`_id`, `_score`, `_source`)
+
+## 配置说明
+
+### 配置文件位置
+
+`config/schema/base/config.yaml`
+
+### 配置内容
+
+Base配置**不包含**以下内容:
+- `mysql_config` - MySQL数据库配置
+- `main_table` - 主表配置
+- `extension_table` - 扩展表配置
+- `source_table` / `source_column` - 字段数据源映射
+
+Base配置**只包含**:
+- ES字段定义(字段类型、分析器、boost等)
+- 查询域(indexes)配置
+- 查询处理配置(query_config)
+- 排序和打分配置(function_score)
+- SPU配置(spu_config)
+
+### 必需字段
+
+- `tenant_id` (KEYWORD, required) - 租户隔离字段
+
+### 主要字段
+
+- `product_id` - 商品ID
+- `title`, `brief`, `description` - 文本搜索字段
+- `seo_title`, `seo_description`, `seo_keywords` - SEO字段
+- `vendor`, `product_type`, `tags`, `category` - 分类和标签字段
+- `min_price`, `max_price`, `compare_at_price` - 价格字段
+- `variants` (nested) - 嵌套变体数组
+
+## 数据导入流程
+
+### 1. 生成测试数据
+
+```bash
+python scripts/generate_test_data.py \
+ --num-spus 100 \
+ --tenant-id "1" \
+ --start-spu-id 1 \
+ --start-sku-id 1 \
+ --output test_data.sql
+```
+
+### 2. 导入测试数据到MySQL
+
+```bash
+python scripts/import_test_data.py \
+ --db-host localhost \
+ --db-port 3306 \
+ --db-database saas \
+ --db-username root \
+ --db-password password \
+ --sql-file test_data.sql \
+ --tenant-id "1"
+```
+
+### 3. 导入数据到Elasticsearch
+
+```bash
+python scripts/ingest_shoplazza.py \
+ --db-host localhost \
+ --db-port 3306 \
+ --db-database saas \
+ --db-username root \
+ --db-password password \
+ --tenant-id "1" \
+ --config base \
+ --es-host http://localhost:9200 \
+ --recreate \
+ --batch-size 500
+```
+
+## API使用
+
+### 搜索接口
+
+**端点**: `POST /search/`
+
+**请求头**:
+```
+X-Tenant-ID: 1
+Content-Type: application/json
+```
+
+**请求体**:
+```json
+{
+ "query": "耳机",
+ "size": 10,
+ "from": 0,
+ "filters": {
+ "category_keyword": "电子产品"
+ },
+ "facets": ["category_keyword", "vendor_keyword"]
+}
+```
+
+**响应格式**:
+```json
+{
+ "results": [
+ {
+ "product_id": "1",
+ "title": "蓝牙耳机 Sony",
+ "handle": "product-1",
+ "description": "高品质无线蓝牙耳机",
+ "vendor": "Sony",
+ "product_type": "电子产品",
+ "price": 199.99,
+ "compare_at_price": 299.99,
+ "currency": "USD",
+ "image_url": "//cdn.example.com/products/1.jpg",
+ "in_stock": true,
+ "variants": [
+ {
+ "variant_id": "1",
+ "title": "黑色",
+ "price": 199.99,
+ "compare_at_price": 299.99,
+ "sku": "SKU-1-1",
+ "stock": 50,
+ "options": {
+ "option1": "黑色"
+ }
+ }
+ ],
+ "relevance_score": 0.95
+ }
+ ],
+ "total": 10,
+ "max_score": 1.0,
+ "facets": [
+ {
+ "field": "category_keyword",
+ "label": "category_keyword",
+ "type": "terms",
+ "values": [
+ {
+ "value": "电子产品",
+ "label": "电子产品",
+ "count": 5,
+ "selected": false
+ }
+ ]
+ }
+ ],
+ "suggestions": [],
+ "related_searches": [],
+ "took_ms": 15,
+ "query_info": {}
+}
+```
+
+### 响应格式说明
+
+#### 主要变化
+
+1. **`results`替代`hits`**:返回字段从`hits`改为`results`
+2. **结构化结果**:每个结果包含`product_id`, `title`, `variants`, `relevance_score`等字段
+3. **无ES内部字段**:不包含`_id`, `_score`, `_source`等ES内部字段
+4. **嵌套variants**:每个商品包含variants数组,每个variant包含完整的变体信息
+5. **相关性分数**:`relevance_score`是ES原始分数(不进行归一化)
+
+#### ProductResult字段
+
+- `product_id` - 商品ID
+- `title` - 商品标题
+- `handle` - 商品handle
+- `description` - 商品描述
+- `vendor` - 供应商/品牌
+- `product_type` - 商品类型
+- `tags` - 标签
+- `price` - 最低价格(min_price)
+- `compare_at_price` - 原价
+- `currency` - 货币单位(默认USD)
+- `image_url` - 主图URL
+- `in_stock` - 是否有库存
+- `variants` - 变体列表
+- `relevance_score` - 相关性分数(ES原始分数)
+
+#### VariantResult字段
+
+- `variant_id` - 变体ID
+- `title` - 变体标题
+- `price` - 价格
+- `compare_at_price` - 原价
+- `sku` - SKU编码
+- `stock` - 库存数量
+- `options` - 选项(颜色、尺寸等)
+
+## 测试
+
+### 运行测试脚本
+
+```bash
+python scripts/test_base.py \
+ --api-url http://localhost:8000 \
+ --tenant-id "1" \
+ --test-tenant-2 "2"
+```
+
+### 测试内容
+
+1. **基本搜索**:测试搜索API基本功能
+2. **响应格式验证**:验证返回格式是否符合要求
+3. **Facets聚合**:测试分面搜索功能
+4. **租户隔离**:验证不同租户的数据隔离
+
+## 常见问题
+
+### Q: 为什么配置中没有MySQL相关配置?
+
+A: 数据源配置和数据导入流程是写死的脚本,不在搜索配置中。搜索配置只关注ES搜索相关的内容。
+
+### Q: 如何为新的租户导入数据?
+
+A: 使用`ingest_shoplazza.py`脚本,指定不同的`--tenant-id`参数即可。
+
+### Q: 如何验证租户隔离是否生效?
+
+A: 使用`test_base.py`脚本,指定两个不同的`--tenant-id`,检查搜索结果是否隔离。
+
+### Q: API返回格式中为什么没有`_id`和`_score`?
+
+A: 为了提供外部友好的API格式,我们移除了ES内部字段,使用`product_id`和`relevance_score`替代。
+
+### Q: 如何添加新的搜索字段?
+
+A: 在`config/schema/base/config.yaml`中添加字段定义,然后重新生成索引映射并重新导入数据。
+
+## 注意事项
+
+1. **tenant_id必需**:所有API请求必须提供`tenant_id`(通过请求头`X-Tenant-ID`或查询参数`tenant_id`)
+2. **索引共享**:所有客户共享`search_products`索引,确保`tenant_id`字段正确设置
+3. **数据导入**:数据导入脚本是写死的,不依赖配置中的MySQL设置
+4. **配置分离**:搜索配置和数据源配置完全分离,提高可维护性
+
diff --git a/docs/搜索API对接指南.md b/docs/搜索API对接指南.md
new file mode 100644
index 0000000..3cb0546
--- /dev/null
+++ b/docs/搜索API对接指南.md
@@ -0,0 +1,1018 @@
+# 搜索API接口对接指南
+
+本文档为搜索服务的使用方提供完整的API对接指南,包括接口说明、请求参数、响应格式和使用示例。
+
+## 目录
+
+1. [快速开始](#快速开始)
+2. [接口概览](#接口概览)
+3. [文本搜索接口](#文本搜索接口)
+4. [图片搜索接口](#图片搜索接口)
+5. [响应格式说明](#响应格式说明)
+6. [常见场景示例](#常见场景示例)
+7. [错误处理](#错误处理)
+8. [最佳实践](#最佳实践)
+
+---
+
+## 快速开始
+
+### 基础信息
+
+- **Base URL**: `http://your-domain:6002` 或 `http://120.76.41.98:6002`
+- **协议**: HTTP/HTTPS
+- **数据格式**: JSON
+- **字符编码**: UTF-8
+- **请求方法**: POST(搜索接口)
+
+### 最简单的搜索请求
+
+```bash
+curl -X POST "http://localhost:6002/search/" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "芭比娃娃"
+ }'
+```
+
+### Python示例
+
+```python
+import requests
+
+url = "http://localhost:6002/search/"
+response = requests.post(url, json={"query": "芭比娃娃"})
+data = response.json()
+print(f"找到 {data['total']} 个结果")
+```
+
+### JavaScript示例
+
+```javascript
+const response = await fetch('http://localhost:6002/search/', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ query: '芭比娃娃'
+ })
+});
+const data = await response.json();
+console.log(`找到 ${data.total} 个结果`);
+```
+
+---
+
+## 接口概览
+
+| 接口 | 方法 | 路径 | 说明 |
+|------|------|------|------|
+| 文本搜索 | POST | `/search/` | 执行文本搜索查询 |
+| 图片搜索 | POST | `/search/image` | 基于图片相似度搜索 |
+| 搜索建议 | GET | `/search/suggestions` | 获取搜索建议(框架,暂未实现) |
+| 获取文档 | GET | `/search/{doc_id}` | 根据ID获取单个文档 |
+| 健康检查 | GET | `/admin/health` | 检查服务状态 |
+
+---
+
+## 文本搜索接口
+
+### 接口信息
+
+- **端点**: `POST /search/`
+- **描述**: 执行文本搜索查询,支持多语言、布尔表达式、过滤器和分面搜索
+
+### 请求参数
+
+#### 完整请求体结构
+
+```json
+{
+ "query": "string (required)",
+ "size": 10,
+ "from": 0,
+ "filters": {},
+ "range_filters": {},
+ "facets": [],
+ "sort_by": "string",
+ "sort_order": "desc",
+ "min_score": 0.0,
+ "debug": false,
+ "user_id": "string",
+ "session_id": "string"
+}
+```
+
+#### 参数详细说明
+
+| 参数 | 类型 | 必填 | 默认值 | 说明 |
+|------|------|------|--------|------|
+| `query` | string | ✅ | - | 搜索查询字符串,支持布尔表达式(AND, OR, RANK, ANDNOT) |
+| `size` | integer | ❌ | 10 | 返回结果数量(1-100) |
+| `from` | integer | ❌ | 0 | 分页偏移量(用于分页) |
+| `filters` | object | ❌ | null | 精确匹配过滤器(见下文) |
+| `range_filters` | object | ❌ | null | 数值范围过滤器(见下文) |
+| `facets` | array | ❌ | null | 分面配置(见下文) |
+| `sort_by` | string | ❌ | null | 排序字段名(如 `min_price`, `max_price`, `title`) |
+| `sort_order` | string | ❌ | "desc" | 排序方向:`asc`(升序)或 `desc`(降序) |
+| `min_score` | float | ❌ | null | 最小相关性分数阈值 |
+| `debug` | boolean | ❌ | false | 是否返回调试信息 |
+| `user_id` | string | ❌ | null | 用户ID(用于个性化,预留) |
+| `session_id` | string | ❌ | null | 会话ID(用于分析,预留) |
+
+### 过滤器详解
+
+#### 1. 精确匹配过滤器 (filters)
+
+用于精确匹配或多值匹配(OR 逻辑)。
+
+**格式**:
+```json
+{
+ "filters": {
+ "category_keyword": "玩具", // 单值:精确匹配
+ "vendor_keyword": ["乐高", "孩之宝"], // 数组:匹配任意值(OR)
+ "product_type_keyword": "益智玩具" // 单值:精确匹配
+ }
+}
+```
+
+**支持的值类型**:
+- 字符串:精确匹配
+- 整数:精确匹配
+- 布尔值:精确匹配
+- 数组:匹配任意值(OR 逻辑)
+
+**常用过滤字段**:
+- `category_keyword`: 类目
+- `vendor_keyword`: 品牌/供应商
+- `product_type_keyword`: 商品类型
+- `tags_keyword`: 标签
+
+#### 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`: 小于 (<)
+
+**注意**: 至少需要指定一个操作符。
+
+**常用范围字段**:
+- `min_price`: 最低价格
+- `max_price`: 最高价格
+- `compare_at_price`: 原价
+- `create_time`: 创建时间
+- `update_time`: 更新时间
+
+#### 3. 分面配置 (facets)
+
+用于生成分面统计(分组聚合),常用于构建筛选器UI。
+
+**简单模式**(字符串数组):
+```json
+{
+ "facets": ["category_keyword", "vendor_keyword"]
+}
+```
+
+**高级模式**(配置对象数组):
+```json
+{
+ "facets": [
+ {
+ "field": "category_keyword",
+ "size": 15,
+ "type": "terms"
+ },
+ {
+ "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`: 字段名(必填)
+- `size`: 返回的分组数量(默认:10,范围:1-100)
+- `type`: 分面类型,`terms`(分组统计)或 `range`(范围统计)
+- `ranges`: 范围定义(仅当 type='range' 时需要)
+
+### 布尔表达式语法
+
+搜索查询支持布尔表达式,提供更灵活的搜索能力。
+
+**支持的操作符**:
+
+| 操作符 | 描述 | 示例 |
+|--------|------|------|
+| `AND` | 所有词必须匹配 | `玩具 AND 乐高` |
+| `OR` | 任意词匹配 | `芭比 OR 娃娃` |
+| `ANDNOT` | 排除特定词 | `玩具 ANDNOT 电动` |
+| `RANK` | 排序加权(不强制匹配) | `玩具 RANK 乐高` |
+| `()` | 分组 | `玩具 AND (乐高 OR 芭比)` |
+
+**操作符优先级**(从高到低):
+1. `()` - 括号
+2. `ANDNOT` - 排除
+3. `AND` - 与
+4. `OR` - 或
+5. `RANK` - 排序
+
+**示例**:
+```
+"芭比娃娃" // 简单查询
+"玩具 AND 乐高" // AND 查询
+"芭比 OR 娃娃" // OR 查询
+"玩具 ANDNOT 电动" // 排除查询
+"玩具 AND (乐高 OR 芭比)" // 复杂查询
+```
+
+---
+
+## 图片搜索接口
+
+### 接口信息
+
+- **端点**: `POST /search/image`
+- **描述**: 基于图片相似度进行搜索,使用图片向量进行语义匹配
+
+### 请求参数
+
+```json
+{
+ "image_url": "string (required)",
+ "size": 10,
+ "filters": {},
+ "range_filters": {}
+}
+```
+
+### 参数说明
+
+| 参数 | 类型 | 必填 | 默认值 | 描述 |
+|------|------|------|--------|------|
+| `image_url` | string | ✅ | - | 查询图片的 URL |
+| `size` | integer | ❌ | 10 | 返回结果数量(1-100) |
+| `filters` | object | ❌ | null | 精确匹配过滤器 |
+| `range_filters` | object | ❌ | null | 数值范围过滤器 |
+
+### 请求示例
+
+```bash
+curl -X POST "http://localhost:6002/search/image" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "image_url": "https://example.com/barbie.jpg",
+ "size": 20,
+ "filters": {
+ "category_keyword": "玩具"
+ },
+ "range_filters": {
+ "min_price": {
+ "lte": 100
+ }
+ }
+ }'
+```
+
+---
+
+## 响应格式说明
+
+### 标准响应结构
+
+```json
+{
+ "results": [
+ {
+ "product_id": "12345",
+ "title": "芭比时尚娃娃",
+ "handle": "barbie-doll",
+ "description": "高品质芭比娃娃",
+ "vendor": "美泰",
+ "product_type": "玩具",
+ "tags": "娃娃, 玩具, 女孩",
+ "price": 89.99,
+ "compare_at_price": 129.99,
+ "currency": "USD",
+ "image_url": "https://example.com/image.jpg",
+ "in_stock": true,
+ "variants": [
+ {
+ "variant_id": "67890",
+ "title": "粉色款",
+ "price": 89.99,
+ "compare_at_price": 129.99,
+ "sku": "BARBIE-001",
+ "stock": 100,
+ "options": {
+ "option1": "粉色",
+ "option2": "标准款"
+ }
+ }
+ ],
+ "relevance_score": 8.5
+ }
+ ],
+ "total": 118,
+ "max_score": 8.5,
+ "facets": [
+ {
+ "field": "category_keyword",
+ "label": "category_keyword",
+ "type": "terms",
+ "values": [
+ {
+ "value": "玩具",
+ "label": "玩具",
+ "count": 85,
+ "selected": false
+ }
+ ]
+ }
+ ],
+ "query_info": {
+ "original_query": "芭比娃娃",
+ "detected_language": "zh",
+ "translations": {
+ "en": "barbie doll"
+ }
+ },
+ "suggestions": [],
+ "related_searches": [],
+ "took_ms": 45,
+ "performance_info": null,
+ "debug_info": null
+}
+```
+
+### 响应字段说明
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `results` | array | 搜索结果列表(ProductResult对象数组) |
+| `results[].product_id` | string | 商品ID |
+| `results[].title` | string | 商品标题 |
+| `results[].price` | float | 价格(min_price) |
+| `results[].variants` | array | 变体列表(SKU列表) |
+| `results[].relevance_score` | float | 相关性分数 |
+| `total` | integer | 匹配的总文档数 |
+| `max_score` | float | 最高相关性分数 |
+| `facets` | array | 分面统计结果 |
+| `query_info` | object | 查询处理信息 |
+| `took_ms` | integer | 搜索耗时(毫秒) |
+
+### ProductResult字段说明
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `product_id` | string | 商品ID(SPU ID) |
+| `title` | string | 商品标题 |
+| `handle` | string | 商品URL handle |
+| `description` | string | 商品描述 |
+| `vendor` | string | 供应商/品牌 |
+| `product_type` | string | 商品类型 |
+| `tags` | string | 标签 |
+| `price` | float | 价格(min_price) |
+| `compare_at_price` | float | 原价 |
+| `currency` | string | 货币单位(默认USD) |
+| `image_url` | string | 主图URL |
+| `in_stock` | boolean | 是否有库存(任意变体有库存即为true) |
+| `variants` | array | 变体列表 |
+| `relevance_score` | float | 相关性分数 |
+
+### VariantResult字段说明
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `variant_id` | string | 变体ID(SKU ID) |
+| `title` | string | 变体标题 |
+| `price` | float | 价格 |
+| `compare_at_price` | float | 原价 |
+| `sku` | string | SKU编码 |
+| `stock` | integer | 库存数量 |
+| `options` | object | 选项(颜色、尺寸等) |
+
+---
+
+## 常见场景示例
+
+### 场景1:商品列表页搜索
+
+**需求**: 搜索"玩具",按价格从低到高排序,显示前20个结果
+
+```json
+{
+ "query": "玩具",
+ "size": 20,
+ "from": 0,
+ "sort_by": "min_price",
+ "sort_order": "asc"
+}
+```
+
+### 场景2:带筛选的商品搜索
+
+**需求**: 搜索"玩具",筛选类目为"益智玩具",价格在50-200之间
+
+```json
+{
+ "query": "玩具",
+ "size": 20,
+ "filters": {
+ "category_keyword": "益智玩具"
+ },
+ "range_filters": {
+ "min_price": {
+ "gte": 50,
+ "lte": 200
+ }
+ }
+}
+```
+
+### 场景3:带分面的商品搜索
+
+**需求**: 搜索"玩具",获取类目和品牌的分面统计,用于构建筛选器
+
+```json
+{
+ "query": "玩具",
+ "size": 20,
+ "facets": [
+ "category_keyword",
+ "vendor_keyword"
+ ]
+}
+```
+
+### 场景4:多条件组合搜索
+
+**需求**: 搜索"玩具",筛选多个品牌,价格范围,并获取分面统计
+
+```json
+{
+ "query": "玩具",
+ "size": 20,
+ "filters": {
+ "vendor_keyword": ["乐高", "孩之宝", "美泰"]
+ },
+ "range_filters": {
+ "min_price": {
+ "gte": 50,
+ "lte": 200
+ }
+ },
+ "facets": [
+ {
+ "field": "category_keyword",
+ "size": 15
+ },
+ {
+ "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}
+ ]
+ }
+ ],
+ "sort_by": "min_price",
+ "sort_order": "asc"
+}
+```
+
+### 场景5:布尔表达式搜索
+
+**需求**: 搜索包含"玩具"和"乐高"的商品,排除"电动"
+
+```json
+{
+ "query": "玩具 AND 乐高 ANDNOT 电动",
+ "size": 20
+}
+```
+
+### 场景6:分页查询
+
+**需求**: 获取第2页结果(每页20条)
+
+```json
+{
+ "query": "玩具",
+ "size": 20,
+ "from": 20
+}
+```
+
+---
+
+## 错误处理
+
+### 错误响应格式
+
+```json
+{
+ "error": "错误信息",
+ "detail": "详细错误信息(可选)"
+}
+```
+
+### 常见错误码
+
+| HTTP状态码 | 说明 | 处理建议 |
+|-----------|------|---------|
+| 200 | 成功 | - |
+| 400 | 请求参数错误 | 检查请求参数格式和必填字段 |
+| 404 | 接口不存在 | 检查接口路径 |
+| 500 | 服务器内部错误 | 联系技术支持 |
+
+### 错误处理示例
+
+**Python**:
+```python
+import requests
+
+try:
+ response = requests.post(url, json=payload, timeout=10)
+ response.raise_for_status()
+ data = response.json()
+except requests.exceptions.HTTPError as e:
+ print(f"HTTP错误: {e}")
+ if response.status_code == 400:
+ error_data = response.json()
+ print(f"错误详情: {error_data.get('detail')}")
+except requests.exceptions.RequestException as e:
+ print(f"请求异常: {e}")
+```
+
+**JavaScript**:
+```javascript
+try {
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(payload)
+ });
+
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(error.error || `HTTP ${response.status}`);
+ }
+
+ const data = await response.json();
+} catch (error) {
+ console.error('搜索失败:', error.message);
+}
+```
+
+---
+
+### 5. 代码示例
+
+**完整的搜索函数(Python)**:
+
+```python
+import requests
+from typing import Dict, Any, Optional, List
+
+class SearchClient:
+ def __init__(self, base_url: str = "http://localhost:6002"):
+ self.base_url = base_url
+ self.timeout = 10
+
+ def search(
+ self,
+ query: str,
+ size: int = 20,
+ from_: int = 0,
+ filters: Optional[Dict] = None,
+ range_filters: Optional[Dict] = None,
+ facets: Optional[List] = None,
+ sort_by: Optional[str] = None,
+ sort_order: str = "desc"
+ ) -> Dict[str, Any]:
+ """
+ 执行搜索查询
+
+ Args:
+ query: 搜索查询字符串
+ size: 返回结果数量
+ from_: 分页偏移量
+ filters: 精确匹配过滤器
+ range_filters: 范围过滤器
+ facets: 分面配置
+ sort_by: 排序字段
+ sort_order: 排序方向
+
+ Returns:
+ 搜索结果字典
+ """
+ url = f"{self.base_url}/search/"
+ payload = {
+ "query": query,
+ "size": size,
+ "from": from_,
+ }
+
+ if filters:
+ payload["filters"] = filters
+ if range_filters:
+ payload["range_filters"] = range_filters
+ if facets:
+ payload["facets"] = facets
+ if sort_by:
+ payload["sort_by"] = sort_by
+ payload["sort_order"] = sort_order
+
+ try:
+ response = requests.post(
+ url,
+ json=payload,
+ timeout=self.timeout
+ )
+ response.raise_for_status()
+ return response.json()
+ except requests.exceptions.RequestException as e:
+ raise Exception(f"搜索请求失败: {e}")
+
+# 使用示例
+client = SearchClient()
+result = client.search(
+ query="玩具",
+ size=20,
+ filters={"category_keyword": "益智玩具"},
+ range_filters={"min_price": {"gte": 50, "lte": 200}},
+ facets=["category_keyword", "vendor_keyword"],
+ sort_by="min_price",
+ sort_order="asc"
+)
+
+print(f"找到 {result['total']} 个结果")
+for product in result['results']:
+ print(f"{product['title']} - ¥{product['price']}")
+```
+
+**完整的搜索函数(JavaScript)**:
+
+```javascript
+class SearchClient {
+ constructor(baseUrl = 'http://localhost:6002') {
+ this.baseUrl = baseUrl;
+ this.timeout = 10000;
+ }
+
+ async search({
+ query,
+ size = 20,
+ from = 0,
+ filters = null,
+ rangeFilters = null,
+ facets = null,
+ sortBy = null,
+ sortOrder = 'desc'
+ }) {
+ const url = `${this.baseUrl}/search/`;
+ const payload = {
+ query,
+ size,
+ from,
+ };
+
+ if (filters) payload.filters = filters;
+ if (rangeFilters) payload.range_filters = rangeFilters;
+ if (facets) payload.facets = facets;
+ if (sortBy) {
+ payload.sort_by = sortBy;
+ payload.sort_order = sortOrder;
+ }
+
+ try {
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(payload),
+ signal: AbortSignal.timeout(this.timeout)
+ });
+
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(error.error || `HTTP ${response.status}`);
+ }
+
+ return await response.json();
+ } catch (error) {
+ throw new Error(`搜索请求失败: ${error.message}`);
+ }
+ }
+}
+
+// 使用示例
+const client = new SearchClient();
+const result = await client.search({
+ query: '玩具',
+ size: 20,
+ filters: { category_keyword: '益智玩具' },
+ rangeFilters: { min_price: { gte: 50, lte: 200 } },
+ facets: ['category_keyword', 'vendor_keyword'],
+ sortBy: 'min_price',
+ sortOrder: 'asc'
+});
+
+console.log(`找到 ${result.total} 个结果`);
+result.results.forEach(product => {
+ console.log(`${product.title} - ¥${product.price}`);
+});
+```
+
+---
+
+## 其他接口
+
+### 搜索建议(框架)
+
+- **端点**: `GET /search/suggestions`
+- **描述**: 返回搜索建议(自动补全/热词)。当前为框架实现,接口和响应格式已经固定,可平滑扩展。
+
+#### 查询参数
+
+| 参数 | 类型 | 必填 | 默认值 | 描述 |
+|------|------|------|--------|------|
+| `q` | string | ✅ | - | 查询字符串(至少 1 个字符) |
+| `size` | integer | ❌ | 5 | 返回建议数量(1-20) |
+| `types` | string | ❌ | `query` | 建议类型(逗号分隔):`query`, `product`, `category`, `brand` |
+
+#### 响应示例
+
+```json
+{
+ "query": "芭",
+ "suggestions": [
+ {
+ "text": "芭比娃娃",
+ "type": "query",
+ "highlight": "芭比娃娃",
+ "popularity": 850
+ }
+ ],
+ "took_ms": 5
+}
+```
+
+#### 请求示例
+
+```bash
+curl "http://localhost:6002/search/suggestions?q=芭&size=5&types=query,product"
+```
+
+---
+
+### 即时搜索(框架)
+
+- **端点**: `GET /search/instant`
+- **描述**: 边输入边搜索,采用轻量参数响应当前输入。底层复用标准搜索能力。
+
+#### 查询参数
+
+| 参数 | 类型 | 必填 | 默认值 | 描述 |
+|------|------|------|--------|------|
+| `q` | string | ✅ | - | 搜索查询(至少 2 个字符) |
+| `size` | integer | ❌ | 5 | 返回结果数量(1-20) |
+
+#### 请求示例
+
+```bash
+curl "http://localhost:6002/search/instant?q=玩具&size=5"
+```
+
+---
+
+### 获取单个文档
+
+- **端点**: `GET /search/{doc_id}`
+- **描述**: 根据文档 ID 获取单个商品详情,用于点击结果后的详情页或排查问题。
+
+#### 路径参数
+
+| 参数 | 类型 | 描述 |
+|------|------|------|
+| `doc_id` | string | 商品或文档 ID |
+
+#### 响应示例
+
+```json
+{
+ "id": "12345",
+ "source": {
+ "title": "芭比时尚娃娃",
+ "min_price": 89.99,
+ "category_keyword": "玩具"
+ }
+}
+```
+
+#### 请求示例
+
+```bash
+curl "http://localhost:6002/search/12345"
+```
+
+---
+
+## 管理接口
+
+### 健康检查
+
+- **端点**: `GET /admin/health`
+- **描述**: 检查服务与依赖(如 Elasticsearch)状态。
+
+```json
+{
+ "status": "healthy",
+ "elasticsearch": "connected",
+ "tenant_id": "tenant1"
+}
+```
+
+---
+
+### 获取配置
+
+- **端点**: `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
+}
+```
+
+---
+
+### 索引统计
+
+- **端点**: `GET /admin/stats`
+- **描述**: 获取索引文档数量与磁盘大小,方便监控。
+
+```json
+{
+ "index_name": "search_tenant1",
+ "document_count": 10000,
+ "size_mb": 523.45
+}
+```
+
+---
+
+## 数据模型
+
+### 商品字段
+
+| 字段名 | 类型 | 描述 |
+|--------|------|------|
+| `product_id` | keyword | 商品 ID(SPU) |
+| `sku_id` | keyword/long | SKU ID(主键) |
+| `title` | text | 商品名称(中文) |
+| `en_title` | text | 商品名称(英文) |
+| `ru_title` | text | 商品名称(俄文) |
+| `category_keyword` | keyword | 类目(精确匹配) |
+| `vendor_keyword` | keyword | 品牌/供应商(精确匹配) |
+| `product_type_keyword` | keyword | 商品类型 |
+| `tags_keyword` | keyword | 标签 |
+| `min_price` | double | 最低价格 |
+| `max_price` | double | 最高价格 |
+| `compare_at_price` | double | 原价 |
+| `create_time` | date | 创建时间 |
+| `update_time` | date | 更新时间 |
+| `in_stock` | boolean | 是否有库存 |
+| `text_embedding` | dense_vector | 文本向量(1024 维) |
+| `image_embedding` | dense_vector | 图片向量(1024 维) |
+
+> 不同租户可自定义字段名称,但最佳实践是对可过滤字段建立 `*_keyword` 版本,对可排序字段显式建 keyword/数值映射。
+
+---
+
+## 常见问题(FAQ)
+
+**Q1: 如何判断一个字段应该用哪种过滤器?**
+`filters` 针对 keyword/布尔/整数字段做精确匹配;`range_filters` 针对数值或日期字段做区间查询。
+
+**Q2: 可以同时使用多个过滤器吗?**
+可以,所有过滤条件为 AND 关系。例如:
+
+```json
+{
+ "filters": {
+ "category_keyword": "玩具",
+ "vendor_keyword": "乐高"
+ },
+ "range_filters": {
+ "min_price": {"gte": 50, "lte": 200}
+ }
+}
+```
+
+**Q4: 分面结果里的 `selected` 字段含义是什么?**
+指示该分面值是否已在当前过滤条件中,前端可据此高亮。
+
+**Q5: 如何自定义排序?**
+设置 `sort_by` 和 `sort_order`,常用字段包括 `min_price`, `max_price`, `title`, `create_time`, `update_time`, `relevance_score`。
+
+**Q6: 如何启用调试模式?**
+添加 `debug: true`,即可在响应中看到 `debug_info`(ES DSL、阶段耗时、打分细节)。
+
+---
+
+## 附录
+
+### 常用字段列表
+
+#### 过滤字段(使用 `*_keyword` 后缀)
+
+- `category_keyword`: 类目
+- `vendor_keyword`: 品牌/供应商
+- `product_type_keyword`: 商品类型
+- `tags_keyword`: 标签
+
+#### 范围字段
+
+- `min_price`: 最低价格
+- `max_price`: 最高价格
+- `compare_at_price`: 原价
+- `create_time`: 创建时间
+- `update_time`: 更新时间
+
+#### 排序字段
+
+- `min_price`: 最低价格
+- `max_price`: 最高价格
+- `title`: 标题(字母序)
+- `create_time`: 创建时间
+- `update_time`: 更新时间
+- `relevance_score`: 相关性分数(默认)
+
+### 支持的分析器
+
+| 分析器 | 语言 | 描述 |
+|--------|------|------|
+| `chinese_ecommerce` | 中文 | 基于 Ansj 的电商优化中文分析器 |
+| `english` | 英文 | 标准英文分析器 |
+| `russian` | 俄文 | 俄文分析器 |
+| `arabic` | 阿拉伯文 | 阿拉伯文分析器 |
+| `spanish` | 西班牙文 | 西班牙文分析器 |
+| `japanese` | 日文 | 日文分析器 |
+
+### 字段类型速查
+
+| 类型 | ES Mapping | 用途 |
+|------|------------|------|
+| `TEXT` | `text` | 全文检索 |
+| `KEYWORD` | `keyword` | 精确匹配、聚合、排序 |
+| `LONG` | `long` | 整数 |
+| `DOUBLE` | `double` | 浮点数 |
+| `DATE` | `date` | 日期时间 |
+| `BOOLEAN` | `boolean` | 布尔值 |
+| `TEXT_EMBEDDING` | `dense_vector` | 文本语义向量 |
+| `IMAGE_EMBEDDING` | `dense_vector` | 图片语义向量 |
+
diff --git a/docs/搜索API速查表.md b/docs/搜索API速查表.md
new file mode 100644
index 0000000..ba9f8d5
--- /dev/null
+++ b/docs/搜索API速查表.md
@@ -0,0 +1,233 @@
+# API 快速参考 (v3.0)
+
+## 基础搜索
+
+```bash
+POST /search/
+{
+ "query": "芭比娃娃",
+ "size": 20
+}
+```
+
+---
+
+## 精确匹配过滤
+
+```bash
+{
+ "filters": {
+ "categoryName_keyword": "玩具", // 单值
+ "brandName_keyword": ["乐高", "美泰"] // 多值(OR)
+ }
+}
+```
+
+---
+
+## 范围过滤
+
+```bash
+{
+ "range_filters": {
+ "price": {
+ "gte": 50, // >=
+ "lte": 200 // <=
+ }
+ }
+}
+```
+
+**操作符**: `gte` (>=), `gt` (>), `lte` (<=), `lt` (<)
+
+---
+
+## 分面搜索
+
+### 简单模式
+
+```bash
+{
+ "facets": ["categoryName_keyword", "brandName_keyword"]
+}
+```
+
+### 高级模式
+
+```bash
+{
+ "facets": [
+ {"field": "categoryName_keyword", "size": 15},
+ {
+ "field": "price",
+ "type": "range",
+ "ranges": [
+ {"key": "0-50", "to": 50},
+ {"key": "50-100", "from": 50, "to": 100}
+ ]
+ }
+ ]
+}
+```
+
+---
+
+## 排序
+
+```bash
+{
+ "sort_by": "min_price",
+ "sort_order": "asc" // asc 或 desc
+}
+```
+
+---
+
+## 布尔表达式
+
+```bash
+{
+ "query": "玩具 AND (乐高 OR 芭比) ANDNOT 电动"
+}
+```
+
+**操作符优先级**: `()` > `ANDNOT` > `AND` > `OR` > `RANK`
+
+---
+
+## 分页
+
+```bash
+{
+ "size": 20, // 每页数量
+ "from": 0 // 偏移量(第1页=0,第2页=20)
+}
+```
+
+---
+
+## 完整示例
+
+```bash
+POST /search/
+{
+ "query": "玩具",
+ "size": 20,
+ "from": 0,
+ "filters": {
+ "categoryName_keyword": ["玩具", "益智玩具"]
+ },
+ "range_filters": {
+ "price": {"gte": 50, "lte": 200}
+ },
+ "facets": [
+ {"field": "brandName_keyword", "size": 15},
+ {"field": "categoryName_keyword", "size": 15}
+ ],
+ "sort_by": "min_price",
+ "sort_order": "asc"
+}
+```
+
+---
+
+## 响应格式
+
+```json
+{
+ "hits": [
+ {
+ "_id": "12345",
+ "_score": 8.5,
+ "_source": {...}
+ }
+ ],
+ "total": 118,
+ "max_score": 8.5,
+ "took_ms": 45,
+ "facets": [
+ {
+ "field": "categoryName_keyword",
+ "label": "商品类目",
+ "type": "terms",
+ "values": [
+ {
+ "value": "玩具",
+ "label": "玩具",
+ "count": 85,
+ "selected": false
+ }
+ ]
+ }
+ ]
+}
+```
+
+---
+
+## 其他端点
+
+```bash
+POST /search/image
+{
+ "image_url": "https://example.com/image.jpg",
+ "size": 20
+}
+
+GET /search/suggestions?q=芭&size=5
+
+GET /search/instant?q=玩具&size=5
+
+GET /search/{doc_id}
+
+GET /admin/health
+GET /admin/config
+GET /admin/stats
+```
+
+---
+
+## Python 快速示例
+
+```python
+import requests
+
+result = requests.post('http://localhost:6002/search/', json={
+ "query": "玩具",
+ "filters": {"categoryName_keyword": "玩具"},
+ "range_filters": {"price": {"gte": 50, "lte": 200}},
+ "facets": ["brandName_keyword"],
+ "sort_by": "min_price",
+ "sort_order": "asc"
+}).json()
+
+print(f"找到 {result['total']} 个结果")
+```
+
+---
+
+## JavaScript 快速示例
+
+```javascript
+const result = await fetch('http://localhost:6002/search/', {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify({
+ query: "玩具",
+ filters: {categoryName_keyword: "玩具"},
+ range_filters: {price: {gte: 50, lte: 200}},
+ facets: ["brandName_keyword"],
+ sort_by: "min_price",
+ sort_order: "asc"
+ })
+}).then(r => r.json());
+
+console.log(`找到 ${result.total} 个结果`);
+```
+
+---
+
+**详细文档**: [API_DOCUMENTATION.md](API_DOCUMENTATION.md)
+**更多示例**: [API_EXAMPLES.md](API_EXAMPLES.md)
+**在线文档**: http://localhost:6002/docs
+
diff --git a/docs/测试Pipeline说明.md b/docs/测试Pipeline说明.md
new file mode 100644
index 0000000..06ca2c1
--- /dev/null
+++ b/docs/测试Pipeline说明.md
@@ -0,0 +1,459 @@
+# 搜索引擎测试流水线指南
+
+## 概述
+
+本文档介绍了搜索引擎项目的完整测试流水线,包括测试环境搭建、测试执行、结果分析等内容。测试流水线设计用于commit前的自动化质量保证。
+
+## 🏗️ 测试架构
+
+### 测试层次
+
+```
+测试流水线
+├── 代码质量检查 (Code Quality)
+│ ├── 代码格式化检查 (Black, isort)
+│ ├── 静态分析 (Flake8, MyPy, Pylint)
+│ └── 安全扫描 (Safety, Bandit)
+│
+├── 单元测试 (Unit Tests)
+│ ├── RequestContext测试
+│ ├── Searcher测试
+│ ├── QueryParser测试
+│ └── BooleanParser测试
+│
+├── 集成测试 (Integration Tests)
+│ ├── 端到端搜索流程测试
+│ ├── 多组件协同测试
+│ └── 错误处理测试
+│
+├── API测试 (API Tests)
+│ ├── REST API接口测试
+│ ├── 参数验证测试
+│ ├── 并发请求测试
+│ └── 错误响应测试
+│
+└── 性能测试 (Performance Tests)
+ ├── 响应时间测试
+ ├── 并发性能测试
+ └── 资源使用测试
+```
+
+### 核心组件
+
+1. **RequestContext**: 请求级别的上下文管理器,用于跟踪测试过程中的所有数据
+2. **测试环境管理**: 自动化启动/停止测试依赖服务
+3. **测试执行引擎**: 统一的测试运行和结果收集
+4. **报告生成系统**: 多格式的测试报告生成
+
+## 🚀 快速开始
+
+### 本地测试环境
+
+1. **启动测试环境**
+ ```bash
+ # 启动所有必要的测试服务
+ ./scripts/start_test_environment.sh
+ ```
+
+2. **运行完整测试套件**
+ ```bash
+ # 运行所有测试
+ python scripts/run_tests.py
+
+ # 或者使用pytest直接运行
+ pytest tests/ -v
+ ```
+
+3. **停止测试环境**
+ ```bash
+ ./scripts/stop_test_environment.sh
+ ```
+
+### CI/CD测试
+
+1. **GitHub Actions**
+ - Push到主分支自动触发
+ - Pull Request自动运行
+ - 手动触发支持
+
+2. **测试报告**
+ - 自动生成并上传
+ - PR评论显示测试摘要
+ - 详细报告下载
+
+## 📋 测试类型详解
+
+### 1. 单元测试 (Unit Tests)
+
+**位置**: `tests/unit/`
+
+**目的**: 测试单个函数、类、模块的功能
+
+**覆盖范围**:
+- `test_context.py`: RequestContext功能测试
+- `test_searcher.py`: Searcher核心功能测试
+- `test_query_parser.py`: QueryParser处理逻辑测试
+
+**运行方式**:
+```bash
+# 运行所有单元测试
+pytest tests/unit/ -v
+
+# 运行特定测试
+pytest tests/unit/test_context.py -v
+
+# 生成覆盖率报告
+pytest tests/unit/ --cov=. --cov-report=html
+```
+
+### 2. 集成测试 (Integration Tests)
+
+**位置**: `tests/integration/`
+
+**目的**: 测试多个组件协同工作的功能
+
+**覆盖范围**:
+- `test_search_integration.py`: 完整搜索流程集成
+- 数据库、ES、搜索器集成测试
+- 错误传播和处理测试
+
+**运行方式**:
+```bash
+# 运行集成测试(需要启动测试环境)
+pytest tests/integration/ -v -m "not slow"
+
+# 运行包含慢速测试的集成测试
+pytest tests/integration/ -v
+```
+
+### 3. API测试 (API Tests)
+
+**位置**: `tests/integration/test_api_integration.py`
+
+**目的**: 测试HTTP API接口的功能和性能
+
+**覆盖范围**:
+- 基本搜索API
+- 参数验证
+- 错误处理
+- 并发请求
+- Unicode支持
+
+**运行方式**:
+```bash
+# 运行API测试
+pytest tests/integration/test_api_integration.py -v
+```
+
+### 4. 性能测试 (Performance Tests)
+
+**目的**: 验证系统性能指标
+
+**测试内容**:
+- 搜索响应时间
+- API并发处理能力
+- 资源使用情况
+
+**运行方式**:
+```bash
+# 运行性能测试
+python scripts/run_performance_tests.py
+```
+
+## 🛠️ 环境配置
+
+### 测试环境要求
+
+1. **Python环境**
+ ```bash
+ # 创建测试环境
+ conda create -n searchengine-test python=3.9
+ conda activate searchengine-test
+
+ # 安装依赖
+ pip install -r requirements.txt
+ pip install pytest pytest-cov pytest-json-report
+ ```
+
+2. **Elasticsearch**
+ ```bash
+ # 使用Docker启动ES
+ docker run -d \
+ --name elasticsearch \
+ -p 9200:9200 \
+ -e "discovery.type=single-node" \
+ -e "xpack.security.enabled=false" \
+ elasticsearch:8.8.0
+ ```
+
+3. **环境变量**
+ ```bash
+ export ES_HOST="http://localhost:9200"
+ export ES_USERNAME="elastic"
+ export ES_PASSWORD="changeme"
+ export API_HOST="127.0.0.1"
+ export API_PORT="6003"
+ export TENANT_ID="test_tenant"
+ export TESTING_MODE="true"
+ ```
+
+### 服务依赖
+
+测试环境需要以下服务:
+
+1. **Elasticsearch** (端口9200)
+ - 存储和搜索测试数据
+ - 支持中文和英文索引
+
+2. **API服务** (端口6003)
+ - FastAPI测试服务
+ - 提供搜索接口
+
+3. **测试数据库**
+ - 预配置的测试索引
+ - 包含测试数据
+
+## 📊 测试报告
+
+### 报告类型
+
+1. **实时控制台输出**
+ - 测试进度显示
+ - 失败详情
+ - 性能摘要
+
+2. **JSON格式报告**
+ ```json
+ {
+ "timestamp": "2024-01-01T10:00:00",
+ "summary": {
+ "total_tests": 150,
+ "passed": 148,
+ "failed": 2,
+ "success_rate": 98.7
+ },
+ "suites": { ... }
+ }
+ ```
+
+3. **文本格式报告**
+ - 人类友好的格式
+ - 包含测试摘要和详情
+ - 适合PR评论
+
+4. **HTML覆盖率报告**
+ - 代码覆盖率可视化
+ - 分支和行覆盖率
+ - 缺失测试高亮
+
+### 报告位置
+
+```
+test_logs/
+├── unit_test_results.json # 单元测试结果
+├── integration_test_results.json # 集成测试结果
+├── api_test_results.json # API测试结果
+├── test_report_20240101_100000.txt # 文本格式摘要
+├── test_report_20240101_100000.json # JSON格式详情
+└── htmlcov/ # HTML覆盖率报告
+```
+
+## 🔄 CI/CD集成
+
+### GitHub Actions工作流
+
+**触发条件**:
+- Push到主分支
+- Pull Request创建/更新
+- 手动触发
+
+**工作流阶段**:
+
+1. **代码质量检查**
+ - 代码格式验证
+ - 静态代码分析
+ - 安全漏洞扫描
+
+2. **单元测试**
+ - 多Python版本矩阵测试
+ - 代码覆盖率收集
+ - 自动上传到Codecov
+
+3. **集成测试**
+ - 服务依赖启动
+ - 端到端功能测试
+ - 错误处理验证
+
+4. **API测试**
+ - 接口功能验证
+ - 参数校验测试
+ - 并发请求测试
+
+5. **性能测试**
+ - 响应时间检查
+ - 资源使用监控
+ - 性能回归检测
+
+6. **测试报告生成**
+ - 结果汇总
+ - 报告上传
+ - PR评论更新
+
+### 工作流配置
+
+**文件**: `.github/workflows/test.yml`
+
+**关键特性**:
+- 并行执行提高效率
+- 服务容器化隔离
+- 自动清理资源
+- 智能缓存依赖
+
+## 🧪 测试最佳实践
+
+### 1. 测试编写原则
+
+- **独立性**: 每个测试应该独立运行
+- **可重复性**: 测试结果应该一致
+- **快速执行**: 单元测试应该快速完成
+- **清晰命名**: 测试名称应该描述测试内容
+
+### 2. 测试数据管理
+
+```python
+# 使用fixture提供测试数据
+@pytest.fixture
+def sample_tenant_config():
+ return TenantConfig(
+ tenant_id="test_tenant",
+ es_index_name="test_products"
+ )
+
+# 使用mock避免外部依赖
+@patch('search.searcher.ESClient')
+def test_search_with_mock_es(mock_es_client, test_searcher):
+ mock_es_client.search.return_value = mock_response
+ result = test_searcher.search("test query")
+ assert result is not None
+```
+
+### 3. RequestContext集成
+
+```python
+def test_with_context(test_searcher):
+ context = create_request_context("test-req", "test-user")
+
+ result = test_searcher.search("test query", context=context)
+
+ # 验证context被正确更新
+ assert context.query_analysis.original_query == "test query"
+ assert context.get_stage_duration("elasticsearch_search") > 0
+```
+
+### 4. 性能测试指南
+
+```python
+def test_search_performance(client):
+ start_time = time.time()
+ response = client.get("/search", params={"q": "test query"})
+ response_time = (time.time() - start_time) * 1000
+
+ assert response.status_code == 200
+ assert response_time < 2000 # 2秒内响应
+```
+
+## 🚨 故障排除
+
+### 常见问题
+
+1. **Elasticsearch连接失败**
+ ```bash
+ # 检查ES状态
+ curl http://localhost:9200/_cluster/health
+
+ # 重启ES服务
+ docker restart elasticsearch
+ ```
+
+2. **测试端口冲突**
+ ```bash
+ # 检查端口占用
+ lsof -i :6003
+
+ # 修改API端口
+ export API_PORT="6004"
+ ```
+
+3. **依赖包缺失**
+ ```bash
+ # 重新安装依赖
+ pip install -r requirements.txt
+ pip install pytest pytest-cov pytest-json-report
+ ```
+
+4. **测试数据问题**
+ ```bash
+ # 重新创建测试索引
+ curl -X DELETE http://localhost:9200/test_products
+ ./scripts/start_test_environment.sh
+ ```
+
+### 调试技巧
+
+1. **详细日志输出**
+ ```bash
+ pytest tests/unit/test_context.py -v -s --tb=long
+ ```
+
+2. **运行单个测试**
+ ```bash
+ pytest tests/unit/test_context.py::TestRequestContext::test_create_context -v
+ ```
+
+3. **调试模式**
+ ```python
+ import pdb; pdb.set_trace()
+ ```
+
+4. **性能分析**
+ ```bash
+ pytest --profile tests/
+ ```
+
+## 📈 持续改进
+
+### 测试覆盖率目标
+
+- **单元测试**: > 90%
+- **集成测试**: > 80%
+- **API测试**: > 95%
+
+### 性能基准
+
+- **搜索响应时间**: < 2秒
+- **API并发处理**: 100 QPS
+- **系统资源使用**: < 80% CPU, < 4GB RAM
+
+### 质量门禁
+
+- **所有测试必须通过**
+- **代码覆盖率不能下降**
+- **性能不能显著退化**
+- **不能有安全漏洞**
+
+## 📚 相关文档
+
+- [RequestContext使用指南](RequestContext_README.md)
+- [API文档](../api/README.md)
+- [配置指南](../config/README.md)
+- [部署指南](Deployment_README.md)
+
+## 🤝 贡献指南
+
+1. 为新功能编写对应的测试
+2. 确保测试覆盖率不下降
+3. 遵循测试命名约定
+4. 更新相关文档
+5. 运行完整测试套件后提交
+
+通过这套完整的测试流水线,我们可以确保搜索引擎代码的质量、性能和可靠性,为用户提供稳定高效的搜索服务。
\ No newline at end of file
diff --git a/docs/测试数据指南.md b/docs/测试数据指南.md
new file mode 100644
index 0000000..2da4375
--- /dev/null
+++ b/docs/测试数据指南.md
@@ -0,0 +1,527 @@
+# 测试数据构造指南 - SearchEngine
+
+本文档说明如何构造测试数据,包括两种数据源的准备和导入流程。
+
+---
+
+## 快速开始
+
+### 1. 构造 Mock 数据(tenant_id=1 和 tenant_id=2)
+
+```bash
+./scripts/mock_data.sh
+```
+
+功能:自动生成 tenant_id=1 的Mock数据,并从CSV导入 tenant_id=2 的数据到MySQL
+
+---
+
+### 2. 从 MySQL → Elasticsearch
+
+```bash
+# 导入 tenant_id=1 的数据(重建索引)
+./scripts/ingest.sh 1 true
+
+# 导入 tenant_id=2 的数据(重建索引)
+./scripts/ingest.sh 2 true
+```
+
+
+**用法**:`./scripts/ingest.sh [recreate_index]`
+- `tenant_id`: 租户ID(1 或 2)
+- `recreate_index`: 是否重建索引(`true`/`false`,默认:`false`)
+
+---
+
+## 完整工作流程
+
+```bash
+# 1. 构造并导入测试数据到MySQL
+./scripts/mock_data.sh
+
+# 2. 导入 tenant_id=1 的数据到ES
+./scripts/ingest.sh 1 true
+
+# 3. 导入 tenant_id=2 的数据到ES
+./scripts/ingest.sh 2 true
+```
+
+---
+
+## 目录
+
+1. [数据说明](#数据说明)
+2. [构造Mock数据(tenant_id=1)](#构造mock数据tenant_id1)
+3. [从CSV导入数据(tenant_id=2)](#从csv导入数据tenant_id2)
+4. [从MySQL导入到Elasticsearch](#从mysql导入到elasticsearch)
+5. [完整工作流程](#完整工作流程)
+6. [常见问题](#常见问题)
+
+---
+
+## 数据说明
+
+系统支持两种测试数据源:
+
+1. **Tenant ID = 1**: 自动生成的Mock数据(使用 `generate_test_data.py` 生成)
+2. **Tenant ID = 2**: 从CSV文件导入的真实数据(使用 `import_tenant2_csv.py` 导入)
+
+### 数据表结构
+
+系统使用店匠标准表结构:
+
+- **SPU表**: `shoplazza_product_spu` - 商品SPU数据
+- **SKU表**: `shoplazza_product_sku` - 商品SKU数据
+
+表结构详见 `INDEX_FIELDS_DOCUMENTATION.md`。
+
+---
+
+## 构造Mock数据(tenant_id=1)
+
+### 使用一键脚本(推荐)
+
+`mock_data.sh` 脚本会自动生成并导入 tenant_id=1 的Mock数据:
+
+```bash
+cd /home/tw/SearchEngine
+./scripts/mock_data.sh
+```
+
+脚本会自动:
+- 生成 1000 个SPU的Mock数据
+- 导入数据到MySQL
+- 自动计算起始ID,避免主键冲突
+
+### 手动分步执行
+
+如果需要自定义参数,可以分步执行:
+
+#### 步骤1: 生成Mock测试数据
+
+```bash
+python scripts/generate_test_data.py \
+ --num-spus 1000 \
+ --tenant-id "1" \
+ --output test_data_tenant1.sql \
+ --db-host 120.79.247.228 \
+ --db-port 3316 \
+ --db-database saas \
+ --db-username saas \
+ --db-password <密码>
+```
+
+参数说明:
+- `--num-spus`: 生成的SPU数量(默认:1000)
+- `--tenant-id`: 租户ID(默认:1)
+- `--output`: 输出的SQL文件路径
+- `--db-host`, `--db-port`, `--db-database`, `--db-username`, `--db-password`: 数据库连接信息
+
+#### 步骤2: 导入数据到MySQL
+
+```bash
+python scripts/import_test_data.py \
+ --db-host 120.79.247.228 \
+ --db-port 3316 \
+ --db-database saas \
+ --db-username saas \
+ --db-password <密码> \
+ --sql-file test_data_tenant1.sql \
+ --tenant-id "1"
+```
+
+参数说明:
+- `--sql-file`: SQL文件路径
+- `--tenant-id`: 租户ID(用于清理旧数据)
+- 其他参数:数据库连接信息
+
+**注意**: 导入会先清理该 tenant_id 的旧数据,再导入新数据。
+
+---
+
+## 从CSV导入数据(tenant_id=2)
+
+### 使用一键脚本(推荐)
+
+`mock_data.sh` 脚本会自动从CSV文件导入 tenant_id=2 的数据:
+
+```bash
+cd /home/tw/SearchEngine
+./scripts/mock_data.sh
+```
+
+**前提条件**: 确保CSV文件存在于以下路径:
+```
+data/customer1/goods_with_pic.5years_congku.csv.shuf.1w
+```
+
+如果CSV文件路径不同,需要修改 `scripts/mock_data.sh` 中的 `TENANT2_CSV_FILE` 变量。
+
+### CSV文件格式要求
+
+CSV文件需要包含以下列(列名不区分大小写):
+
+- `skuId` - SKU ID
+- `name` - 商品名称
+- `name_pinyin` - 拼音(可选)
+- `create_time` - 创建时间(格式:YYYY-MM-DD HH:MM:SS)
+- `ruSkuName` - 俄文SKU名称(可选)
+- `enSpuName` - 英文SPU名称(可选)
+- `categoryName` - 类别名称
+- `supplierName` - 供应商名称
+- `brandName` - 品牌名称
+- `file_id` - 文件ID(可选)
+- `days_since_last_update` - 更新天数(可选)
+- `id` - 商品ID(可选)
+- `imageUrl` - 图片URL(可选)
+
+### 手动分步执行
+
+如果需要自定义参数,可以分步执行:
+
+#### 步骤1: 从CSV生成SQL文件
+
+```bash
+python scripts/import_tenant2_csv.py \
+ --csv-file data/customer1/goods_with_pic.5years_congku.csv.shuf.1w \
+ --tenant-id "2" \
+ --output customer1_data.sql \
+ --db-host 120.79.247.228 \
+ --db-port 3316 \
+ --db-database saas \
+ --db-username saas \
+ --db-password <密码>
+```
+
+参数说明:
+- `--csv-file`: CSV文件路径
+- `--tenant-id`: 租户ID(默认:2)
+- `--output`: 输出的SQL文件路径
+- 其他参数:数据库连接信息
+
+#### 步骤2: 导入数据到MySQL
+
+```bash
+python scripts/import_test_data.py \
+ --db-host 120.79.247.228 \
+ --db-port 3316 \
+ --db-database saas \
+ --db-username saas \
+ --db-password <密码> \
+ --sql-file customer1_data.sql \
+ --tenant-id "2"
+```
+
+**注意**:
+- CSV导入会先清理该 tenant_id 的旧数据,再导入新数据
+- 脚本会自动计算起始ID,避免主键冲突
+
+---
+
+## 从MySQL导入到Elasticsearch
+
+数据导入到MySQL后,需要使用 `ingest.sh` 脚本将数据从MySQL导入到Elasticsearch。
+
+### 基本用法
+
+```bash
+./scripts/ingest.sh [recreate_index]
+```
+
+参数说明:
+- `tenant_id`: **必需**,租户ID,用于筛选数据库中的数据
+- `recreate_index`: 可选,是否删除并重建索引(true/false,默认:false)
+
+### 使用示例
+
+#### 重建索引并导入数据(推荐首次导入)
+
+```bash
+# 导入tenant_id=1的数据并重建索引
+./scripts/ingest.sh 1 true
+
+# 导入tenant_id=2的数据并重建索引
+./scripts/ingest.sh 2 true
+```
+
+#### 增量导入(不重建索引)
+
+```bash
+# 增量导入tenant_id=1的数据
+./scripts/ingest.sh 1 false
+
+# 增量导入tenant_id=2的数据
+./scripts/ingest.sh 2 false
+```
+
+### 手动执行
+
+如果需要自定义参数,可以手动执行:
+
+```bash
+python scripts/ingest_shoplazza.py \
+ --db-host 120.79.247.228 \
+ --db-port 3316 \
+ --db-database saas \
+ --db-username saas \
+ --db-password <密码> \
+ --tenant-id 1 \
+ --es-host http://localhost:9200 \
+ --recreate \
+ --batch-size 500
+```
+
+参数说明:
+- `--db-host`, `--db-port`, `--db-database`, `--db-username`, `--db-password`: MySQL连接信息
+- `--tenant-id`: 租户ID(必需)
+- `--es-host`: Elasticsearch地址
+- `--recreate`: 是否重建索引
+- `--batch-size`: 批量处理大小(默认:500)
+
+### 检查可用的 tenant_id
+
+如果导入时显示 "No documents to index",脚本会自动显示调试信息,包括:
+- 该 tenant_id 的统计信息(总数、活跃数、已删除数)
+- 数据库中存在的其他 tenant_id 列表
+
+也可以直接查询数据库:
+
+```sql
+-- 查看有哪些 tenant_id
+SELECT tenant_id, COUNT(*) as count,
+ SUM(CASE WHEN deleted = 0 THEN 1 ELSE 0 END) as active
+FROM shoplazza_product_spu
+GROUP BY tenant_id;
+
+-- 检查特定 tenant_id 的数据
+SELECT COUNT(*) FROM shoplazza_product_spu
+WHERE tenant_id = 2 AND deleted = 0;
+```
+
+**注意**:
+- 只有 `deleted=0` 的记录会被导入
+- 首次运行会下载模型文件(BGE-M3和CN-CLIP),大约需要10-30分钟
+- 确保MySQL中存在对应 tenant_id 的数据
+
+---
+
+## 完整工作流程
+
+### 完整示例:构造并导入所有测试数据
+
+```bash
+# 1. 构造并导入 tenant_id=1 的Mock数据到MySQL
+./scripts/mock_data.sh
+
+# 脚本会自动完成:
+# - 生成 tenant_id=1 的Mock数据(1000个SPU)
+# - 从CSV导入 tenant_id=2 的数据
+# - 导入数据到MySQL
+
+# 2. 从MySQL导入 tenant_id=1 的数据到ES
+./scripts/ingest.sh 1 true
+
+# 3. 从MySQL导入 tenant_id=2 的数据到ES
+./scripts/ingest.sh 2 true
+
+# 4. 验证数据导入
+curl http://localhost:9200/search_products/_count
+```
+
+### 分步执行示例
+
+如果需要更细粒度的控制,可以分步执行:
+
+```bash
+# ===== Part 1: 构造 tenant_id=1 的Mock数据 =====
+
+# 1.1 生成Mock数据
+python scripts/generate_test_data.py \
+ --num-spus 1000 \
+ --tenant-id "1" \
+ --output test_data_tenant1.sql \
+ --db-host 120.79.247.228 \
+ --db-port 3316 \
+ --db-database saas \
+ --db-username saas \
+ --db-password <密码>
+
+# 1.2 导入到MySQL
+python scripts/import_test_data.py \
+ --db-host 120.79.247.228 \
+ --db-port 3316 \
+ --db-database saas \
+ --db-username saas \
+ --db-password <密码> \
+ --sql-file test_data_tenant1.sql \
+ --tenant-id "1"
+
+# ===== Part 2: 从CSV导入 tenant_id=2 的数据 =====
+
+# 2.1 从CSV生成SQL
+python scripts/import_tenant2_csv.py \
+ --csv-file data/customer1/goods_with_pic.5years_congku.csv.shuf.1w \
+ --tenant-id "2" \
+ --output customer1_data.sql \
+ --db-host 120.79.247.228 \
+ --db-port 3316 \
+ --db-database saas \
+ --db-username saas \
+ --db-password <密码>
+
+# 2.2 导入到MySQL
+python scripts/import_test_data.py \
+ --db-host 120.79.247.228 \
+ --db-port 3316 \
+ --db-database saas \
+ --db-username saas \
+ --db-password <密码> \
+ --sql-file customer1_data.sql \
+ --tenant-id "2"
+
+# ===== Part 3: 从MySQL导入到ES =====
+
+# 3.1 导入 tenant_id=1 的数据到ES
+./scripts/ingest.sh 1 true
+
+# 3.2 导入 tenant_id=2 的数据到ES
+./scripts/ingest.sh 2 true
+
+# ===== Part 4: 验证 =====
+
+# 4.1 检查ES中的数据量
+curl http://localhost:9200/search_products/_count
+
+# 4.2 测试搜索
+curl -X POST http://localhost:6002/search/ \
+ -H "Content-Type: application/json" \
+ -H "X-Tenant-ID: 1" \
+ -d '{"query": "玩具", "size": 10}'
+```
+
+---
+
+## 常见问题
+
+### Q1: 数据导入失败
+
+**症状**: `Error during data ingestion`
+
+**解决方案**:
+```bash
+# 检查MySQL数据是否存在
+mysql -h 120.79.247.228 -P 3316 -u saas -p saas -e \
+ "SELECT COUNT(*) FROM shoplazza_product_spu WHERE tenant_id=1"
+
+# 检查ES索引是否存在
+curl http://localhost:9200/search_products
+
+# 查看详细错误日志
+python scripts/ingest_shoplazza.py --tenant-id 1 --recreate
+```
+
+### Q2: CSV文件找不到
+
+**症状**: `ERROR: CSV file not found`
+
+**解决方案**:
+```bash
+# 检查CSV文件是否存在
+ls -lh data/customer1/goods_with_pic.5years_congku.csv.shuf.1w
+
+# 如果路径不同,修改 scripts/mock_data.sh 中的 TENANT2_CSV_FILE 变量
+```
+
+### Q3: 导入时没有数据
+
+**症状**: `WARNING: No documents to index` 或 `Transformed 0 SPU documents`
+
+**可能原因**:
+1. 数据库中不存在该 tenant_id 的数据
+2. 数据都被标记为 `deleted=1`
+3. tenant_id 类型不匹配
+
+**解决步骤**:
+
+1. **查看调试信息**: 脚本会自动显示调试信息,包括:
+ ```
+ DEBUG: tenant_id=1000: total=0, active=0, deleted=0
+ DEBUG: Available tenant_ids in shoplazza_product_spu:
+ tenant_id=1: total=100, active=100
+ tenant_id=2: total=50, active=50
+ ```
+
+2. **检查数据库**: 直接查询MySQL确认数据
+ ```sql
+ -- 查看有哪些 tenant_id
+ SELECT tenant_id, COUNT(*) as count,
+ SUM(CASE WHEN deleted = 0 THEN 1 ELSE 0 END) as active
+ FROM shoplazza_product_spu
+ GROUP BY tenant_id;
+
+ -- 检查特定 tenant_id 的数据
+ SELECT COUNT(*) FROM shoplazza_product_spu
+ WHERE tenant_id = 2 AND deleted = 0;
+ ```
+
+3. **如果数据库中没有数据,需要先导入数据**:
+ - 如果有CSV文件,使用CSV导入脚本
+ - 如果没有CSV文件,可以使用mock数据生成脚本
+
+4. **使用正确的 tenant_id**: 根据调试信息显示的可用 tenant_id,使用正确的值重新导入
+ ```bash
+ ./scripts/ingest.sh 2 true # 使用调试信息中显示的 tenant_id
+ ```
+
+### Q4: 模型下载慢或失败
+
+**症状**: 首次运行时模型下载很慢或超时
+
+**解决方案**:
+```bash
+# 跳过embedding快速测试(不推荐,但可以快速验证流程)
+# 注意:这会导致搜索功能不完整
+
+# 或手动下载模型到指定目录
+# TEXT_MODEL_DIR=/data/tw/models/bge-m3
+# IMAGE_MODEL_DIR=/data/tw/models/cn-clip
+```
+
+### Q5: 内存不足
+
+**症状**: `Out of memory`
+
+**解决方案**:
+```bash
+# 减少批量大小
+python scripts/ingest_shoplazza.py \
+ --tenant-id 1 \
+ --batch-size 200 # 默认500,可以减少到100-200
+```
+
+### Q6: 主键冲突
+
+**症状**: `Duplicate entry` 错误
+
+**解决方案**:
+- Mock数据脚本会自动计算起始ID,避免冲突
+- 如果仍有冲突,可以手动清理旧数据:
+ ```sql
+ DELETE FROM shoplazza_product_spu WHERE tenant_id = 1;
+ DELETE FROM shoplazza_product_sku WHERE tenant_id = 1;
+ ```
+
+---
+
+## 相关文档
+
+- **使用文档**: `USAGE_GUIDE.md` - 环境、启动、配置、日志查看
+- **字段说明文档**: `INDEX_FIELDS_DOCUMENTATION.md` - 索引字段详细说明
+- **API接口文档**: `API_INTEGRATION_GUIDE.md` - 完整的API对接指南
+- **README**: `README.md` - 项目概述和快速开始
+
+---
+
+**文档版本**: v2.0
+**最后更新**: 2024-12
+
diff --git a/docs/环境配置说明.md b/docs/环境配置说明.md
new file mode 100644
index 0000000..841674a
--- /dev/null
+++ b/docs/环境配置说明.md
@@ -0,0 +1,123 @@
+
+
+
+## 2. Python 运行环境
+
+```bash
+# 1. 激活 Conda
+source /home/tw/miniconda3/etc/profile.d/conda.sh
+conda activate searchengine
+
+# 如果部署到新机器,不存在 searchengine 环境时,需要初始化环境:
+cd /home/tw/SearchEngine
+pip install -r requirements.txt
+```
+
+---
+
+## 3. 外部服务与端口
+
+| 服务 | 默认地址 | 说明 |
+|------|----------|------|
+| Elasticsearch | `http://localhost:9200` | 可通过 Docker 单节点启动 |
+| MySQL | `120.79.247.228:3316` | 存放店匠 SPU/SKU 数据 |
+| Redis(可选) | `localhost:6479` | Embedding/翻译缓存 |
+
+示例:使用 Docker 启动 Elasticsearch
+
+```bash
+docker run -d \
+ --name elasticsearch \
+ -p 9200:9200 \
+ -e "discovery.type=single-node" \
+ -e "ES_JAVA_OPTS=-Xms2g -Xmx2g" \
+ elasticsearch:8.11.0
+```
+
+---
+
+## 4. 环境变量与 `.env` 模板
+
+在项目根目录创建 `.env`,并根据环境替换敏感信息:
+
+```env
+# MySQL
+DB_HOST=120.79.247.228
+DB_PORT=3316
+DB_DATABASE=saas
+DB_USERNAME=saas
+DB_PASSWORD=P89cZHS5d7dFyc9R
+
+# Elasticsearch
+ES_HOST=http://localhost:9200
+ES_USERNAME=essa
+ES_PASSWORD=4hOaLaf41y2VuI8y
+
+# Redis(可选)
+REDIS_HOST=localhost
+REDIS_PORT=6479
+REDIS_PASSWORD=BMfv5aI31kgHWtlx
+
+# DeepL 翻译
+DEEPL_AUTH_KEY=c9293ab4-ad25-479b-919f-ab4e63b429ed
+
+# API
+API_HOST=0.0.0.0
+API_PORT=6002
+```
+
+---
+
+## 5. 服务凭证速查
+
+| 项目 | 值 |
+|------|----|
+| **MySQL** | host `120.79.247.228`, port `3316`, user `saas`, password `P89cZHS5d7dFyc9R` |
+| **Elasticsearch** | host `http://localhost:9200`, user `essa`, password `4hOaLaf41y2VuI8y` |
+| **Redis(可选)** | host `localhost`, port `6479`, password `BMfv5aI31kgHWtlx` |
+| **DeepL** | `c9293ab4-ad25-479b-919f-ab4e63b429ed` |
+
+> 所有凭证仅用于本地/测试环境,生产环境需替换并妥善保管。
+
+---
+
+## 6. 店匠数据源说明
+
+SearchEngine 以 MySQL 中的店匠标准表为权威数据源:
+
+- `shoplazza_product_spu`:SPU 商品主表
+- `shoplazza_product_sku`:SKU 变体表
+
+### `shoplazza_product_sku` 字段节选
+
+| 字段 | 类型 | 描述 |
+|------|------|------|
+| `id` | bigint(20) | SKU 主键 |
+| `spu_id` | bigint(20) | 对应 SPU |
+| `shop_id` | bigint(20) | 店铺 ID |
+| `shoplazza_product_id` | varchar(64) | 店匠商品 ID |
+| `title` | varchar(500) | 变体标题 |
+| `sku` | varchar(100) | SKU 编码 |
+| `price` | decimal(10,2) | 售价 |
+| `compare_at_price` | decimal(10,2) | 原价 |
+| `option1/2/3` | varchar(255) | 颜色/尺码等选项 |
+| `inventory_quantity` | int(11) | 库存 |
+| `image_src` | varchar(500) | 图片 |
+| `tenant_id` | bigint(20) | 租户 |
+| `create_time` | datetime | 创建时间 |
+| `update_time` | datetime | 更新时间 |
+| `deleted` | bit(1) | 逻辑删除标记 |
+
+> 完整字段、索引映射与 ES 对应关系详见 `INDEX_FIELDS_DOCUMENTATION.md`。
+
+---
+
+## 7. 相关脚本
+
+- `scripts/mock_data.sh`:一次性生成 Tenant1 Mock + Tenant2 CSV 数据并导入 MySQL
+- `scripts/ingest.sh [recreate]`:从 MySQL 写入 Elasticsearch
+- `run.sh` / `restart.sh`:服务启动/重启
+
+更多脚本参数、日志与验证命令参见 `USAGE_GUIDE.md` 与 `TEST_DATA_GUIDE.md`。
+
+
diff --git a/docs/系统设计文档.md b/docs/系统设计文档.md
new file mode 100644
index 0000000..df25f92
--- /dev/null
+++ b/docs/系统设计文档.md
@@ -0,0 +1,724 @@
+# 搜索引擎通用化开发进度
+
+## 项目概述
+
+对后端搜索技术 做通用化。
+通用化的本质 是 对于各种业务数据、各种检索需求,都可以 用少量定制+配置化 来实现效果。
+
+
+**通用化的本质**:对于各种业务数据、各种检索需求,都可以用少量定制+配置化来实现效果。
+
+---
+
+## 1. 原始数据层的约定
+
+### 1.1 店匠主表
+
+所有租户共用以下主表:
+- `shoplazza_product_sku` - SKU级别商品数据
+- `shoplazza_product_spu` - SPU级别商品数据
+
+### 1.2 索引结构(SPU维度)
+
+**统一索引架构**:
+- 所有客户共享同一个Elasticsearch索引:`search_products`
+- 索引粒度:SPU级别(每个文档代表一个SPU)
+- 数据隔离:通过`tenant_id`字段实现租户隔离
+- 嵌套结构:每个SPU文档包含嵌套的`variants`数组(SKU变体)
+
+**索引文档结构**:
+```json
+{
+ "tenant_id": "1",
+ "product_id": "123",
+ "title": "蓝牙耳机",
+ "variants": [
+ {
+ "variant_id": "456",
+ "title": "黑色",
+ "price": 199.99,
+ "sku": "SKU-123-1",
+ "stock": 50
+ }
+ ],
+ "min_price": 199.99,
+ "max_price": 299.99
+}
+```
+
+### 1.3 配置化方案
+
+**配置分离原则**:
+- **搜索配置**:只包含ES字段定义、查询域、排序规则等搜索相关配置
+- **数据源配置**:不在搜索配置中,由Pipeline层(脚本)决定
+- **数据导入流程**:写死的脚本,不依赖配置
+
+统一通过配置文件定义:
+1. ES 字段定义(字段类型、分析器、boost等)
+2. ES mapping 结构生成
+3. 查询域配置(indexes)
+4. 排序和打分配置(function_score)
+
+**注意**:配置中**不包含**以下内容:
+- `mysql_config` - MySQL数据库配置
+- `main_table` / `extension_table` - 数据表配置
+- `source_table` / `source_column` - 字段数据源映射
+
+---
+
+## 2. 配置系统实现
+
+### 2.1 应用结构配置(字段定义)
+
+**配置文件位置**:`config/schema/{tenant_id}_config.yaml`
+
+**配置内容**:定义了 ES 的输入数据有哪些字段、关联 MySQL 的哪些字段。
+
+**实现情况**:
+
+#### 字段类型支持
+- **TEXT**:文本字段,支持多语言分析器
+- **KEYWORD**:关键词字段,用于精确匹配和聚合
+- **TEXT_EMBEDDING**:文本向量字段(1024维,dot_product相似度)
+- **IMAGE_EMBEDDING**:图片向量字段(1024维,dot_product相似度)
+- **INT/LONG**:整数类型
+- **FLOAT/DOUBLE**:浮点数类型
+- **DATE**:日期类型
+- **BOOLEAN**:布尔类型
+
+#### 分析器支持
+- **chinese_ecommerce**:中文电商分词器(index_ansj/query_ansj)
+- **english**:英文分析器
+- **russian**:俄文分析器
+- **arabic**:阿拉伯文分析器
+- **spanish**:西班牙文分析器
+- **japanese**:日文分析器
+- **standard**:标准分析器
+- **keyword**:关键词分析器
+
+#### 字段配置示例(Base配置)
+
+```yaml
+fields:
+ # 租户隔离字段(必需)
+ - name: "tenant_id"
+ type: "KEYWORD"
+ required: true
+ index: true
+ store: true
+
+ # 商品标识字段
+ - name: "product_id"
+ type: "KEYWORD"
+ required: true
+ index: true
+ store: true
+
+ # 文本搜索字段
+ - name: "title"
+ type: "TEXT"
+ analyzer: "chinese_ecommerce"
+ boost: 3.0
+ index: true
+ store: true
+
+ - name: "seo_keywords"
+ type: "TEXT"
+ analyzer: "chinese_ecommerce"
+ boost: 2.0
+ index: true
+ store: true
+
+ # 嵌套variants字段
+ - name: "variants"
+ type: "JSON"
+ nested: true
+ nested_properties:
+ variant_id:
+ type: "keyword"
+ price:
+ type: "float"
+ sku:
+ type: "keyword"
+```
+
+**注意**:配置中**不包含**`source_table`和`source_column`,数据源映射由Pipeline层决定。
+
+**实现模块**:
+- `config/config_loader.py` - 配置加载器
+- `config/field_types.py` - 字段类型定义
+- `indexer/mapping_generator.py` - ES mapping 生成器
+- `indexer/data_transformer.py` - 数据转换器
+
+### 2.2 索引结构配置(查询域配置)
+
+**配置内容**:定义了 ES 的字段索引 mapping 配置,支持各个域的查询,包括默认域的查询。
+
+**实现情况**:
+
+#### 域(Domain)配置
+每个域定义了:
+- 域名称(如 `default`, `title`, `category`, `brand`)
+- 域标签(中文描述)
+- 搜索字段列表
+- 默认分析器
+- 权重(boost)
+- **多语言字段映射**(`language_field_mapping`)
+
+#### 多语言字段映射
+
+支持将不同语言的查询路由到对应的字段:
+
+```yaml
+indexes:
+ - name: "default"
+ label: "默认索引"
+ fields:
+ - "name"
+ - "enSpuName"
+ - "ruSkuName"
+ - "categoryName"
+ - "brandName"
+ analyzer: "chinese_ecommerce"
+ boost: 1.0
+ language_field_mapping:
+ zh:
+ - "name"
+ - "categoryName"
+ - "brandName"
+ en:
+ - "enSpuName"
+ ru:
+ - "ruSkuName"
+
+ - name: "title"
+ label: "标题索引"
+ fields:
+ - "name"
+ - "enSpuName"
+ - "ruSkuName"
+ analyzer: "chinese_ecommerce"
+ boost: 2.0
+ language_field_mapping:
+ zh:
+ - "name"
+ en:
+ - "enSpuName"
+ ru:
+ - "ruSkuName"
+```
+
+**工作原理**:
+1. 检测查询语言(中文、英文、俄文等)
+2. 如果查询语言在 `language_field_mapping` 中,使用原始查询搜索对应语言的字段
+3. 将查询翻译到其他支持的语言,分别搜索对应语言的字段
+4. 组合多个语言查询的结果,提高召回率
+
+**实现模块**:
+- `search/multilang_query_builder.py` - 多语言查询构建器
+- `query/query_parser.py` - 查询解析器(支持语言检测和翻译)
+
+---
+
+## 3. 数据导入流程
+
+### 3.1 数据源
+
+**店匠标准表**(Base配置使用):
+- `shoplazza_product_spu` - SPU级别商品数据
+- `shoplazza_product_sku` - SKU级别商品数据
+
+**其他客户表**(tenant1等):
+- 使用各自的数据源表和扩展表
+
+### 3.2 数据导入方式
+
+**Pipeline层决定数据源**:
+- 数据导入流程是写死的脚本,不依赖配置
+- 配置只关注ES搜索相关的内容
+- 数据源映射逻辑写死在转换器代码中
+
+#### Base配置数据导入(店匠通用)
+
+**脚本**:`scripts/ingest_shoplazza.py`
+
+**数据流程**:
+1. **数据加载**:从MySQL读取`shoplazza_product_spu`和`shoplazza_product_sku`表
+2. **数据转换**(`indexer/spu_transformer.py`):
+ - 按`spu_id`和`tenant_id`关联SPU和SKU数据
+ - 将SKU数据聚合为嵌套的`variants`数组
+ - 计算扁平化价格字段(`min_price`, `max_price`, `compare_at_price`)
+ - 字段映射(写死在代码中,不依赖配置)
+ - 注入`tenant_id`字段
+3. **索引创建**:
+ - 根据配置生成ES mapping
+ - 创建或更新`search_products`索引
+4. **批量入库**:
+ - 批量写入ES(默认每批500条)
+ - 错误处理和重试机制
+
+**命令行工具**:
+```bash
+python scripts/ingest_shoplazza.py \
+ --db-host localhost \
+ --db-port 3306 \
+ --db-database saas \
+ --db-username root \
+ --db-password password \
+ --tenant-id "1" \
+ --config base \
+ --es-host http://localhost:9200 \
+ --recreate \
+ --batch-size 500
+```
+
+#### 其他客户数据导入
+
+- 使用各自的数据转换器(如`indexer/data_transformer.py`)
+- 数据源映射逻辑写死在各自的转换器中
+- 共享`search_products`索引,通过`tenant_id`隔离
+
+**实现模块**:
+- `indexer/spu_transformer.py` - SPU数据转换器(Base配置)
+- `indexer/data_transformer.py` - 通用数据转换器(其他客户)
+- `indexer/bulk_indexer.py` - 批量索引器
+- `scripts/ingest_shoplazza.py` - 店匠数据导入脚本
+
+---
+
+## 4. QueryParser 实现
+
+
+### 4.1 查询改写(Query Rewriting)
+
+配置词典的key是query,value是改写后的查询表达式,比如。比如品牌词 改写为在brand|query OR name|query,类别词、标签词等都可以放进去。纠错、规范化、查询改写等 都可以通过这个词典来配置。
+**实现情况**:
+
+#### 配置方式
+在 `query_config.rewrite_dictionary` 中配置查询改写规则:
+
+```yaml
+query_config:
+ enable_query_rewrite: true
+ rewrite_dictionary:
+ "芭比": "brand:芭比 OR name:芭比娃娃"
+ "玩具": "category:玩具"
+ "消防": "category:消防 OR name:消防"
+```
+
+#### 功能特性
+- **精确匹配**:查询完全匹配词典 key 时,替换为 value
+- **部分匹配**:查询包含词典 key 时,替换该部分
+- **支持布尔表达式**:value 可以是复杂的布尔表达式(AND, OR, 域查询等)
+
+#### 实现模块
+- `query/query_rewriter.py` - 查询改写器
+- `query/query_parser.py` - 查询解析器(集成改写功能)
+
+### 4.2 翻译(Translation)
+
+**实现情况**:
+
+#### 配置方式
+```yaml
+query_config:
+ supported_languages:
+ - "zh"
+ - "en"
+ - "ru"
+ default_language: "zh"
+ enable_translation: true
+ translation_service: "deepl"
+ translation_api_key: null # 通过环境变量设置
+```
+
+#### 功能特性
+1. **语言检测**:自动检测查询语言
+2. **智能翻译**:
+ - 如果查询是中文,翻译为英文、俄文
+ - 如果查询是英文,翻译为中文、俄文
+ - 如果查询是其他语言,翻译为所有支持的语言
+3. **域感知翻译**:
+ - 如果域有 `language_field_mapping`,只翻译到映射中存在的语言
+ - 避免不必要的翻译,提高效率
+4. **翻译缓存**:缓存翻译结果,避免重复调用 API
+
+#### 工作流程
+```
+查询输入 → 语言检测 → 确定目标语言 → 翻译 → 多语言查询构建
+```
+
+#### 实现模块
+- `query/language_detector.py` - 语言检测器
+- `query/translator.py` - 翻译器(DeepL API)
+- `query/query_parser.py` - 查询解析器(集成翻译功能)
+
+### 4.3 文本向量化(Text Embedding)
+
+如果配置打开了text_embedding查询,并且query 包含了default域的查询,那么要把default域的查询词转向量,后面searcher会用这个向量参与查询。
+
+**实现情况**:
+
+#### 配置方式
+```yaml
+query_config:
+ enable_text_embedding: true
+```
+
+#### 功能特性
+1. **条件生成**:
+ - 仅当 `enable_text_embedding=true` 时生成向量
+ - 仅对 `default` 域查询生成向量
+2. **向量模型**:BGE-M3 模型(1024维向量)
+3. **用途**:用于语义搜索(KNN 检索)
+
+#### 实现模块
+- `embeddings/bge_encoder.py` - BGE 文本编码器
+- `query/query_parser.py` - 查询解析器(集成向量生成)
+
+---
+
+## 5. Searcher 实现
+
+参考opensearch,他们自己定义的一套索引结构配置、支持自定义的一套检索表达式、排序表达式,这是各个客户进行配置化的基础,包括索引结构配置、排序策略配置。
+比如各种业务过滤策略 可以简单的通过表达式满足,比如brand|耐克 AND cate2|xxx。指定字段排序可以通过排序的表达式实现。
+
+查询默认在default域,相也会对这个域的查询做一些相关性的重点优化,包括融合语义相关性、多语言相关性(可以基于配置 将查询翻译到指定语言并在对应的语言的字段进行查询)来弥补传统查询分析手段(比如查询改写 纠错 词权重等)的不足,也支持通过配置一些词表转为泛查询模式来优化相关性。
+
+### 5.1 布尔表达式解析
+
+**实现情况**:
+
+#### 支持的运算符
+- **AND**:所有项必须匹配
+- **OR**:任意项匹配
+- **RANK**:排序增强(类似 OR 但影响排序)
+- **ANDNOT**:排除(第一项匹配,第二项不匹配)
+- **()**:括号分组
+
+#### 优先级(从高到低)
+1. `()` - 括号
+2. `ANDNOT` - 排除
+3. `AND` - 与
+4. `OR` - 或
+5. `RANK` - 排序
+
+#### 示例
+```
+laptop AND (gaming OR professional) ANDNOT cheap
+```
+
+#### 实现模块
+- `search/boolean_parser.py` - 布尔表达式解析器
+- `search/searcher.py` - 搜索器(集成布尔解析)
+
+### 5.2 多语言搜索
+
+**实现情况**:
+
+#### 工作原理
+1. **查询解析**:
+ - 提取域(如 `title:查询` → 域=`title`,查询=`查询`)
+ - 检测查询语言
+ - 生成翻译
+2. **多语言查询构建**:
+ - 如果域有 `language_field_mapping`:
+ - 使用检测到的语言查询对应字段(boost * 1.5)
+ - 使用翻译后的查询搜索其他语言字段(boost * 1.0)
+ - 如果域没有 `language_field_mapping`:
+ - 使用所有字段进行搜索
+3. **查询组合**:
+ - 多个语言查询组合为 `should` 子句
+ - 提高召回率
+
+#### 示例
+```
+查询: "芭比娃娃"
+域: default
+检测语言: zh
+
+生成的查询:
+- 中文查询 "芭比娃娃" → 搜索 name, categoryName, brandName (boost * 1.5)
+- 英文翻译 "Barbie doll" → 搜索 enSpuName (boost * 1.0)
+- 俄文翻译 "Кукла Барби" → 搜索 ruSkuName (boost * 1.0)
+```
+
+#### 实现模块
+- `search/multilang_query_builder.py` - 多语言查询构建器
+- `search/searcher.py` - 搜索器(使用多语言构建器)
+
+### 5.3 相关性计算(Ranking)
+
+**实现情况**:
+
+#### 当前实现
+**公式**:`bm25() + 0.2 * text_embedding_relevance()`
+
+- **bm25()**:BM25 文本相关性得分
+ - 包括多语言打分
+ - 内部通过配置翻译为多种语言
+ - 分别到对应的字段搜索
+ - 中文字段使用中文分词器,英文字段使用英文分词器
+- **text_embedding_relevance()**:文本向量相关性得分(KNN 检索的打分)
+ - 权重:0.2
+
+#### 配置方式
+```yaml
+ranking:
+ expression: "bm25() + 0.2*text_embedding_relevance()"
+ description: "BM25 text relevance combined with semantic embedding similarity"
+```
+
+#### 扩展性
+- 支持表达式配置(未来可扩展)
+- 支持自定义函数(如 `timeliness()`, `field_value()`)
+
+#### 实现模块
+- `search/ranking_engine.py` - 排序引擎
+- `search/searcher.py` - 搜索器(集成排序功能)
+
+---
+
+## 6. 已完成功能总结
+
+### 6.1 配置系统
+- ✅ 字段定义配置(类型、分析器、来源表/列)
+- ✅ 索引域配置(多域查询、多语言映射)
+- ✅ 查询配置(改写词典、翻译配置)
+- ✅ 排序配置(表达式配置)
+- ✅ 配置验证(字段存在性、类型检查、分析器匹配)
+
+### 6.2 数据索引
+- ✅ 数据转换(字段映射、类型转换)
+- ✅ 向量生成(文本向量、图片向量)
+- ✅ 向量缓存(避免重复计算)
+- ✅ 批量索引(错误处理、重试机制)
+- ✅ ES mapping 自动生成
+
+### 6.3 查询处理
+- ✅ 查询改写(词典配置)
+- ✅ 语言检测
+- ✅ 多语言翻译(DeepL API)
+- ✅ 文本向量化(BGE-M3)
+- ✅ 域提取(支持 `domain:query` 语法)
+
+### 6.4 搜索功能
+- ✅ 布尔表达式解析(AND, OR, RANK, ANDNOT, 括号)
+- ✅ 多语言查询构建(语言路由、字段映射)
+- ✅ 语义搜索(KNN 检索)
+- ✅ 相关性排序(BM25 + 向量相似度)
+- ✅ 结果聚合(Faceted Search)
+
+### 6.5 API 服务
+- ✅ RESTful API(FastAPI)
+- ✅ 搜索接口(文本搜索、图片搜索)
+- ✅ 文档查询接口
+- ✅ 前端界面(HTML + JavaScript)
+- ✅ 租户隔离(tenant_id过滤)
+
+### 6.6 Base配置(店匠通用)
+- ✅ SPU级别索引结构
+- ✅ 嵌套variants字段
+- ✅ 统一索引(search_products)
+- ✅ 租户隔离(tenant_id)
+- ✅ 配置简化(移除MySQL相关配置)
+
+---
+
+## 7. 技术栈
+
+- **后端**:Python 3.6+
+- **搜索引擎**:Elasticsearch
+- **数据库**:MySQL(Shoplazza)
+- **向量模型**:BGE-M3(文本)、CN-CLIP(图片)
+- **翻译服务**:DeepL API
+- **API 框架**:FastAPI
+- **前端**:HTML + JavaScript
+
+---
+
+## 8. API响应格式
+
+### 8.1 外部友好格式
+
+API返回格式不包含ES内部字段(`_id`, `_score`, `_source`),使用外部友好的格式:
+
+**响应结构**:
+```json
+{
+ "results": [
+ {
+ "product_id": "123",
+ "title": "蓝牙耳机",
+ "variants": [
+ {
+ "variant_id": "456",
+ "price": 199.99,
+ "sku": "SKU-123-1",
+ "stock": 50
+ }
+ ],
+ "relevance_score": 0.95
+ }
+ ],
+ "total": 10,
+ "facets": [...],
+ "suggestions": [],
+ "related_searches": []
+}
+```
+
+**主要变化**:
+- 结构化结果(`ProductResult`和`VariantResult`)
+- 嵌套variants数组
+- 无ES内部字段
+
+### 8.2 租户隔离
+
+所有API请求必须提供`tenant_id`:
+- 请求头:`X-Tenant-ID: 1`
+- 或查询参数:`?tenant_id=1`
+
+搜索时自动添加`tenant_id`过滤,确保数据隔离。
+
+### 8.3 数据接口约定
+
+**统一的数据约定格式**:所有API接口使用 Pydantic 模型进行数据验证和序列化。
+
+#### 8.3.1 数据流模式
+
+系统采用统一的数据流模式,确保数据在各层之间的一致性:
+
+**数据流转路径**:
+```
+API Request (JSON)
+ ↓
+Pydantic 验证 → 结构化模型(RangeFilter, FacetConfig 等)
+ ↓
+Searcher(透传)
+ ↓
+ES Query Builder → model_dump() 转换为字典
+ ↓
+ES Query (字典)
+ ↓
+Elasticsearch
+```
+
+#### 8.3.2 Facets 配置数据流
+
+**输入格式**:`List[Union[str, FacetConfig]]`
+
+- **简单模式**:字符串列表(字段名),使用默认配置
+ ```json
+ ["categoryName_keyword", "brandName_keyword"]
+ ```
+
+- **高级模式**:FacetConfig 对象列表,支持自定义配置
+ ```json
+ [
+ {
+ "field": "categoryName_keyword",
+ "size": 15,
+ "type": "terms"
+ },
+ {
+ "field": "price",
+ "type": "range",
+ "ranges": [
+ {"key": "0-50", "to": 50},
+ {"key": "50-100", "from": 50, "to": 100}
+ ]
+ }
+ ]
+ ```
+
+**数据流**:
+1. API 层:接收 `List[Union[str, FacetConfig]]`
+2. Searcher 层:透传,不做转换
+3. ES Query Builder:只接受 `str` 或 `FacetConfig`,自动处理两种格式
+4. 输出:转换为 ES 聚合查询
+
+#### 8.3.3 Range Filters 数据流
+
+**输入格式**:`Dict[str, RangeFilter]`
+
+**RangeFilter 模型**:
+```python
+class RangeFilter(BaseModel):
+ gte: Optional[Union[float, str]] # 大于等于
+ gt: Optional[Union[float, str]] # 大于
+ lte: Optional[Union[float, str]] # 小于等于
+ lt: Optional[Union[float, str]] # 小于
+```
+
+**示例**:
+```json
+{
+ "price": {"gte": 50, "lte": 200},
+ "created_at": {"gte": "2023-01-01T00:00:00Z"}
+}
+```
+
+**数据流**:
+1. API 层:接收 `Dict[str, RangeFilter]`,Pydantic 自动验证
+2. Searcher 层:透传 `Dict[str, RangeFilter]`
+3. ES Query Builder:调用 `range_filter.model_dump()` 转换为字典
+4. 输出:ES range 查询(支持数值和日期)
+
+**特性**:
+- 自动验证:确保至少指定一个边界值(gte, gt, lte, lt)
+- 类型支持:支持数值(float)和日期时间字符串(ISO 格式)
+- 统一约定:所有范围过滤都使用 RangeFilter 模型
+
+#### 8.3.4 响应 Facets 数据流
+
+**输出格式**:`List[FacetResult]`
+
+**FacetResult 模型**:
+```python
+class FacetResult(BaseModel):
+ field: str # 字段名
+ label: str # 显示标签
+ type: Literal["terms", "range"] # 分面类型
+ values: List[FacetValue] # 分面值列表
+ total_count: Optional[int] # 总文档数
+```
+
+**数据流**:
+1. ES Response:返回聚合结果(字典格式)
+2. Searcher 层:构建 `List[FacetResult]` 对象
+3. API 层:直接返回 `List[FacetResult]`(Pydantic 自动序列化为 JSON)
+
+**优势**:
+- 类型安全:使用 Pydantic 模型确保数据结构一致性
+- 自动序列化:模型自动转换为 JSON,无需手动处理
+- 统一约定:所有响应都使用标准化的 Pydantic 模型
+
+#### 8.3.5 统一约定的好处
+
+1. **类型安全**:使用 Pydantic 模型提供运行时类型检查和验证
+2. **代码一致性**:所有层使用相同的数据模型,减少转换错误
+3. **自动文档**:FastAPI 自动生成 API 文档(基于 Pydantic 模型)
+4. **易于维护**:修改数据结构只需更新模型定义
+5. **数据验证**:自动验证输入数据,减少错误处理代码
+
+**实现模块**:
+- `api/models.py` - 所有 Pydantic 模型定义
+- `api/result_formatter.py` - 结果格式化器(ES 响应 → Pydantic 模型)
+- `search/es_query_builder.py` - ES 查询构建器(Pydantic 模型 → ES 查询)
+
+## 9. 配置文件示例
+
+**Base配置**(店匠通用):`config/schema/base/config.yaml`
+
+**其他客户配置**:`config/schema/tenant1/config.yaml`
+
+---
+
+## 9. 相关文档
+
+- `MULTILANG_FEATURE.md` - 多语言功能详细说明
+- `QUICKSTART.md` - 快速开始指南
+- `HighLevelDesign.md` - 高层设计文档
+- `IMPLEMENTATION_SUMMARY.md` - 实现总结
+- `商品数据源入ES配置规范.md` - 数据源配置规范
diff --git a/docs/索引字段说明.md b/docs/索引字段说明.md
new file mode 100644
index 0000000..3b85168
--- /dev/null
+++ b/docs/索引字段说明.md
@@ -0,0 +1,223 @@
+# 索引字段说明文档
+
+本文档详细说明了 Elasticsearch 索引中所有字段的类型、索引方式、数据来源等信息。
+
+## 索引基本信息
+
+- **索引名称**: `search_products`
+- **索引级别**: SPU级别(商品级别)
+- **数据结构**: SPU文档包含嵌套的variants(SKU)数组
+
+## 字段说明表
+
+### 基础字段
+
+| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | 说明 |
+|-----------|-----------|---------|---------|-----------|-----------|-------------|------|
+| tenant_id | KEYWORD | 是 | 精确匹配 | SPU表 | tenant_id | BIGINT | 租户ID,用于多租户隔离 |
+| product_id | KEYWORD | 是 | 精确匹配 | SPU表 | id | BIGINT | 商品ID(SPU ID) |
+| handle | KEYWORD | 是 | 精确匹配 | SPU表 | handle | VARCHAR(255) | 商品URL handle |
+
+### 文本搜索字段
+
+| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | Boost权重 | 说明 |
+|-----------|-----------|---------|---------|-----------|-----------|-------------|-----------|------|
+| title | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | title | VARCHAR(512) | 3.0 | 商品标题,权重最高 |
+| brief | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | brief | VARCHAR(512) | 1.5 | 商品简介 |
+| description | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | description | TEXT | 1.0 | 商品详细描述 |
+
+### SEO字段
+
+| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | Boost权重 | 是否返回 | 说明 |
+|-----------|-----------|---------|---------|-----------|-----------|-------------|-----------|---------|------|
+| seo_title | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | seo_title | VARCHAR(512) | 2.0 | 否 | SEO标题,用于提升相关性 |
+| seo_description | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | seo_description | TEXT | 1.5 | 否 | SEO描述 |
+| seo_keywords | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | seo_keywords | VARCHAR(1024) | 2.0 | 否 | SEO关键词 |
+
+### 分类和标签字段
+
+| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | Boost权重 | 是否返回 | 说明 |
+|-----------|-----------|---------|---------|-----------|-----------|-------------|-----------|---------|------|
+| vendor | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | vendor | VARCHAR(255) | 1.5 | 是 | 供应商/品牌(文本搜索) |
+| vendor_keyword | KEYWORD | 是 | 精确匹配 | SPU表 | vendor | VARCHAR(255) | - | 否 | 供应商/品牌(精确匹配,用于过滤) |
+| product_type | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | category | VARCHAR(255) | 1.5 | 是 | 商品类型(文本搜索) |
+| product_type_keyword | KEYWORD | 是 | 精确匹配 | SPU表 | category | VARCHAR(255) | - | 否 | 商品类型(精确匹配,用于过滤) |
+| tags | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | tags | VARCHAR(1024) | 1.0 | 是 | 标签(文本搜索) |
+| tags_keyword | KEYWORD | 是 | 精确匹配 | SPU表 | tags | VARCHAR(1024) | - | 否 | 标签(精确匹配,用于过滤) |
+| category | TEXT | 是 | chinese_ecommerce分析器 | SPU表 | category | VARCHAR(255) | 1.5 | 是 | 类目(文本搜索) |
+| category_keyword | KEYWORD | 是 | 精确匹配 | SPU表 | category | VARCHAR(255) | - | 否 | 类目(精确匹配,用于过滤) |
+
+### 价格字段
+
+| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | 说明 |
+|-----------|-----------|---------|---------|-----------|-----------|-------------|------|
+| min_price | FLOAT | 是 | 数值范围 | SKU表(聚合计算) | price | DECIMAL(10,2) | 最低价格(从所有SKU中取最小值) |
+| max_price | FLOAT | 是 | 数值范围 | SKU表(聚合计算) | price | DECIMAL(10,2) | 最高价格(从所有SKU中取最大值) |
+| compare_at_price | FLOAT | 是 | 数值范围 | SKU表(聚合计算) | compare_at_price | DECIMAL(10,2) | 原价(从所有SKU中取最大值) |
+
+**价格计算逻辑**:
+- `min_price`: 取该SPU下所有SKU的price字段的最小值
+- `max_price`: 取该SPU下所有SKU的price字段的最大值
+- `compare_at_price`: 取该SPU下所有SKU的compare_at_price字段的最大值(如果存在)
+
+### 图片字段
+
+| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | 说明 |
+|-----------|-----------|---------|---------|-----------|-----------|-------------|------|
+| image_url | KEYWORD | 否 | 不索引 | SPU表 | image_src | VARCHAR(500) | 商品主图URL,仅用于展示 |
+
+### 文本嵌入字段
+
+| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | 说明 |
+|-----------|-----------|---------|---------|-----------|-----------|-------------|------|
+| title_embedding | TEXT_EMBEDDING | 是 | 向量相似度(dot_product) | 计算生成 | title | VARCHAR(512) | 标题的文本向量(1024维),用于语义搜索 |
+
+**说明**:
+- 向量维度:1024
+- 相似度算法:dot_product(点积)
+- 数据来源:基于title字段通过BGE-M3模型生成
+
+### 时间字段
+
+| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | 是否返回 | 说明 |
+|-----------|-----------|---------|---------|-----------|-----------|-------------|---------|------|
+| create_time | DATE | 是 | 日期范围 | SPU表 | create_time | DATETIME | 是 | 创建时间 |
+| update_time | DATE | 是 | 日期范围 | SPU表 | update_time | DATETIME | 是 | 更新时间 |
+| shoplazza_created_at | DATE | 是 | 日期范围 | SPU表 | shoplazza_created_at | DATETIME | 否 | 店匠系统创建时间 |
+| shoplazza_updated_at | DATE | 是 | 日期范围 | SPU表 | shoplazza_updated_at | DATETIME | 否 | 店匠系统更新时间 |
+
+### 嵌套Variants字段(SKU级别)
+
+| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | 说明 |
+|-----------|-----------|---------|---------|-----------|-----------|-------------|------|
+| variants | JSON (nested) | 是 | 嵌套对象 | SKU表 | - | - | 商品变体数组(嵌套结构) |
+
+#### Variants子字段
+
+| 索引字段名 | ES字段类型 | 是否索引 | 索引方式 | 数据来源表 | 表中字段名 | 表中字段类型 | 说明 |
+|-----------|-----------|---------|---------|-----------|-----------|-------------|------|
+| variants.variant_id | keyword | 是 | 精确匹配 | SKU表 | id | BIGINT | 变体ID(SKU ID) |
+| variants.title | text | 是 | chinese_ecommerce分析器 | SKU表 | title | VARCHAR(500) | 变体标题 |
+| variants.price | float | 是 | 数值范围 | SKU表 | price | DECIMAL(10,2) | 变体价格 |
+| variants.compare_at_price | float | 是 | 数值范围 | SKU表 | compare_at_price | DECIMAL(10,2) | 变体原价 |
+| variants.sku | keyword | 是 | 精确匹配 | SKU表 | sku | VARCHAR(100) | SKU编码 |
+| variants.stock | long | 是 | 数值范围 | SKU表 | inventory_quantity | INT(11) | 库存数量 |
+| variants.options | object | 是 | 对象 | SKU表 | option1/option2/option3 | VARCHAR(255) | 选项(颜色、尺寸等) |
+
+**Variants结构说明**:
+- `variants` 是一个嵌套对象数组,每个元素代表一个SKU
+- 使用ES的nested类型,支持对嵌套字段进行独立查询和过滤
+- `options` 对象包含 `option1`、`option2`、`option3` 三个字段,分别对应SKU表中的选项值
+
+## 字段类型说明
+
+### ES字段类型映射
+
+| ES字段类型 | Elasticsearch映射 | 用途 |
+|-----------|------------------|------|
+| KEYWORD | keyword | 精确匹配、过滤、聚合、排序 |
+| TEXT | text | 全文检索(支持分词) |
+| FLOAT | float | 浮点数(价格、权重等) |
+| LONG | long | 整数(库存、计数等) |
+| DATE | date | 日期时间 |
+| TEXT_EMBEDDING | dense_vector | 文本向量(1024维) |
+| JSON | object/nested | 嵌套对象 |
+
+### 分析器说明
+
+| 分析器名称 | 语言 | 说明 |
+|-----------|------|------|
+| chinese_ecommerce | 中文 | Ansj中文分词器(电商优化),用于中文文本的分词和搜索 |
+
+## 索引配置
+
+### 索引设置
+
+- **分片数**: 1
+- **副本数**: 0
+- **刷新间隔**: 30秒
+
+### 查询域(Query Domains)
+
+系统定义了多个查询域,用于在不同场景下搜索不同的字段组合:
+
+1. **default(默认索引)**: 搜索所有文本字段
+ - 包含字段:title, brief, description, seo_title, seo_description, seo_keywords, vendor, product_type, tags, category
+ - Boost: 1.0
+
+2. **title(标题索引)**: 仅搜索标题相关字段
+ - 包含字段:title, seo_title
+ - Boost: 2.0
+
+3. **vendor(品牌索引)**: 仅搜索品牌字段
+ - 包含字段:vendor
+ - Boost: 1.5
+
+4. **category(类目索引)**: 仅搜索类目字段
+ - 包含字段:category
+ - Boost: 1.5
+
+5. **tags(标签索引)**: 搜索标签和SEO关键词
+ - 包含字段:tags, seo_keywords
+ - Boost: 1.0
+
+## 数据转换规则
+
+### 数据类型转换
+
+1. **BIGINT → KEYWORD**: 数字ID转换为字符串(如 `product_id`, `variant_id`)
+2. **DECIMAL → FLOAT**: 价格字段从DECIMAL转换为FLOAT
+3. **INT → LONG**: 库存数量从INT转换为LONG
+4. **DATETIME → DATE**: 时间字段转换为ISO格式字符串
+
+### 特殊处理
+
+1. **价格聚合**: 从多个SKU的价格中计算min_price、max_price、compare_at_price
+2. **图片URL处理**: 如果image_src不是完整URL,会自动添加协议前缀
+3. **选项合并**: 将SKU表的option1、option2、option3合并为options对象
+
+## 注意事项
+
+1. **多租户隔离**: 所有查询必须包含 `tenant_id` 过滤条件
+2. **嵌套查询**: 查询variants字段时需要使用nested查询语法
+3. **字段命名**: 用于过滤的字段应使用 `*_keyword` 后缀的字段
+4. **向量搜索**: title_embedding字段用于语义搜索,需要配合文本查询使用
+5. **Boost权重**: 不同字段的boost权重影响搜索结果的相关性排序
+
+## 数据来源表结构
+
+### SPU表(shoplazza_product_spu)
+
+主要字段:
+- `id`: BIGINT - 主键ID
+- `tenant_id`: BIGINT - 租户ID
+- `handle`: VARCHAR(255) - URL handle
+- `title`: VARCHAR(512) - 商品标题
+- `brief`: VARCHAR(512) - 商品简介
+- `description`: TEXT - 商品描述
+- `vendor`: VARCHAR(255) - 供应商/品牌
+- `category`: VARCHAR(255) - 类目
+- `tags`: VARCHAR(1024) - 标签
+- `seo_title`: VARCHAR(512) - SEO标题
+- `seo_description`: TEXT - SEO描述
+- `seo_keywords`: VARCHAR(1024) - SEO关键词
+- `image_src`: VARCHAR(500) - 图片URL
+- `create_time`: DATETIME - 创建时间
+- `update_time`: DATETIME - 更新时间
+- `shoplazza_created_at`: DATETIME - 店匠创建时间
+- `shoplazza_updated_at`: DATETIME - 店匠更新时间
+
+### SKU表(shoplazza_product_sku)
+
+主要字段:
+- `id`: BIGINT - 主键ID(对应variant_id)
+- `spu_id`: BIGINT - SPU ID(关联字段)
+- `title`: VARCHAR(500) - 变体标题
+- `price`: DECIMAL(10,2) - 价格
+- `compare_at_price`: DECIMAL(10,2) - 原价
+- `sku`: VARCHAR(100) - SKU编码
+- `inventory_quantity`: INT(11) - 库存数量
+- `option1`: VARCHAR(255) - 选项1
+- `option2`: VARCHAR(255) - 选项2
+- `option3`: VARCHAR(255) - 选项3
+
diff --git a/商品数据源入ES配置规范.md b/商品数据源入ES配置规范.md
deleted file mode 100644
index a503d64..0000000
--- a/商品数据源入ES配置规范.md
+++ /dev/null
@@ -1,221 +0,0 @@
-根据您提供的内容,我将其整理为规范的Markdown格式:
-
-# ES索引配置文档
-
-## 1. 全局配置
-
-### 1.1 文本字段相关性设定
-需要修改所有text字段相关性算法-BM25算法的默认参数:
-```json
-"similarity": {
- "default": {
- "type": "BM25",
- "b": "0.0",
- "k1": "0.0"
- }
-}
-```
-
-### 1.2 索引分片设定
-- `number_of_replicas`:0/1
-- `number_of_shards`:设置建议 分片数 <= ES集群的总CPU核心个数/ (副本数 + 1)
-
-### 1.3 索引刷新时间设定
-- `refresh_interval`:默认30S,根据客户需要进行调整
-```json
-"refresh_interval": "30s"
-```
-
-## 2. 单个字段配置
-
-| 分析方式 | 字段预处理和ES输入格式要求 | 对应ES mapping配置 | 备注 |
-|---------|--------------------------|-------------------|------|
-| 电商通用分析-中文 | - | ```json { "type": "text", "analyzer": "index_ansj", "search_analyzer": "query_ansj" } ``` | - |
-| 文本-多语言向量化 | 调用"文本向量化"模块得到1024维向量 | ```json { "type": "dense_vector", "dims": 1024, "index": true, "similarity": "dot_product" } ``` | 1. 依赖"文本向量化"模块
2. 如果定期全量,需要对向量化结果做缓存 |
-| 图片-向量化 | 调用"图片向量化"模块得到1024维向量 | ```json { "type": "nested", "properties": { "vector": { "type": "dense_vector", "dims": 1024, "similarity": "dot_product" }, "url": { "type": "text" } } } ``` | 1. 依赖"图片向量化"模块
2. 如果定期全量,需要对向量化结果做缓存 |
-| 关键词 | ES输入格式:list或者单个值 | ```json {"type": "keyword"} ``` | - |
-| 电商通用分析-英文 | - | ```json {"type": "text", "analyzer": "english"} ``` | - |
-| 电商通用分析-阿拉伯文 | - | ```json {"type": "text", "analyzer": "arabic"} ``` | - |
-| 电商通用分析-西班牙文 | - | ```json {"type": "text", "analyzer": "spanish"} ``` | - |
-| 电商通用分析-俄文 | - | ```json {"type": "text", "analyzer": "russian"} ``` | - |
-| 电商通用分析-日文 | - | ```json {"type": "text", "analyzer": "japanese"} ``` | - |
-| 数值-整数 | - | ```json {"type": "long"} ``` | - |
-| 数值-浮点型 | - | ```json {"type": "float"} ``` | - |
-| 分值 | 输入是float,配置处理方式:log, pow, sigmoid等 | TODO:给代码, log | - |
-| 子串 | - | 暂时不支持 | - |
-| ngram匹配或前缀匹配或边缘前缀匹配 | - | 暂时不支持 | 以后根据需要再添加 |
-
-这样整理后,文档结构更加清晰,表格格式规范,便于阅读和理解。
-
-
-参考 opensearch:
-
-数据接口
-文本相关性字段
-向量相关性字段
-3. 模块提取
-文本向量化
-import sys
-import torch
-from sentence_transformers import SentenceTransformer
-import time
-import threading
-from modelscope import snapshot_download
-from transformers import AutoModel
-import os
-from openai import OpenAI
-from config.logging_config import get_app_logger
-
-# Get logger for this module
-logger = get_app_logger(__name__)
-
-class BgeEncoder:
- _instance = None
- _lock = threading.Lock()
-
- def __new__(cls, model_dir='Xorbits/bge-m3'):
- with cls._lock:
- if cls._instance is None:
- cls._instance = super(BgeEncoder, cls).__new__(cls)
- logger.info("[BgeEncoder] Creating a new instance with model directory: %s", model_dir)
- cls._instance.model = SentenceTransformer(snapshot_download(model_dir))
- logger.info("[BgeEncoder] New instance has been created")
- return cls._instance
-
- def encode(self, sentences, normalize_embeddings=True, device='cuda'):
- # Move model to specified device
- if device == 'gpu':
- device = 'cuda'
- self.model = self.model.to(device)
- embeddings = self.model.encode(sentences, normalize_embeddings=normalize_embeddings, device=device, show_progress_bar=False)
- return embeddings
-图片向量化
-import sys
-import os
-import io
-import requests
-import torch
-import numpy as np
-from PIL import Image
-import logging
-import threading
-from typing import List, Optional, Union
-from config.logging_config import get_app_logger
-import cn_clip.clip as clip
-from cn_clip.clip import load_from_name
-
-# Get logger for this module
-logger = get_app_logger(__name__)
-
-# DEFAULT_MODEL_NAME = "ViT-L-14-336" # ["ViT-B-16", "ViT-L-14", "ViT-L-14-336", "ViT-H-14", "RN50"]
-DEFAULT_MODEL_NAME = "ViT-H-14"
-MODEL_DOWNLOAD_DIR = "/data/tw/uat/EsSearcher"
-
-class CLIPImageEncoder:
- """CLIP Image Encoder for generating image embeddings using cn_clip"""
-
- _instance = None
- _lock = threading.Lock()
-
- def __new__(cls, model_name=DEFAULT_MODEL_NAME, device=None):
- with cls._lock:
- if cls._instance is None:
- cls._instance = super(CLIPImageEncoder, cls).__new__(cls)
- logger.info(f"[CLIPImageEncoder] Creating new instance with model: {model_name}")
- cls._instance._initialize_model(model_name, device)
- return cls._instance
-
- def _initialize_model(self, model_name, device):
- """Initialize the CLIP model using cn_clip"""
- try:
- self.device = device if device else ("cuda" if torch.cuda.is_available() else "cpu")
- self.model, self.preprocess = load_from_name(model_name, device=self.device, download_root=MODEL_DOWNLOAD_DIR)
- self.model.eval()
- self.model_name = model_name
- logger.info(f"[CLIPImageEncoder] Model {model_name} initialized successfully on device {self.device}")
-
- except Exception as e:
- logger.error(f"[CLIPImageEncoder] Failed to initialize model: {str(e)}")
- raise
-
- def validate_image(self, image_data: bytes) -> Image.Image:
- """Validate image data and return PIL Image if valid"""
- try:
- image_stream = io.BytesIO(image_data)
- image = Image.open(image_stream)
- image.verify()
- image_stream.seek(0)
- image = Image.open(image_stream)
- if image.mode != 'RGB':
- image = image.convert('RGB')
- return image
- except Exception as e:
- raise ValueError(f"Invalid image data: {str(e)}")
-
- def download_image(self, url: str, timeout: int = 10) -> bytes:
- """Download image from URL"""
- try:
- if url.startswith(('http://', 'https://')):
- response = requests.get(url, timeout=timeout)
- if response.status_code != 200:
- raise ValueError(f"HTTP {response.status_code}")
- return response.content
- else:
- # Local file path
- with open(url, 'rb') as f:
- return f.read()
- except Exception as e:
- raise ValueError(f"Failed to download image from {url}: {str(e)}")
-
- def preprocess_image(self, image: Image.Image, max_size: int = 1024) -> Image.Image:
- """Preprocess image for CLIP model"""
- # Resize if too large
- if max(image.size) > max_size:
- ratio = max_size / max(image.size)
- new_size = tuple(int(dim * ratio) for dim in image.size)
- image = image.resize(new_size, Image.Resampling.LANCZOS)
- return image
-
- def encode_text(self, text):
- """Encode text to embedding vector using cn_clip"""
- text_data = clip.tokenize([text] if type(text) == str else text).to(self.device)
- with torch.no_grad():
- text_features = self.model.encode_text(text_data)
- text_features /= text_features.norm(dim=-1, keepdim=True)
- return text_features
-
- def encode_image(self, image: Image.Image) -> Optional[np.ndarray]:
- """Encode image to embedding vector using cn_clip"""
- if not isinstance(image, Image.Image):
- raise ValueError("CLIPImageEncoder.encode_image Input must be a PIL.Image")
-
- try:
- infer_data = self.preprocess(image).unsqueeze(0).to(self.device)
- with torch.no_grad():
- image_features = self.model.encode_image(infer_data)
- image_features /= image_features.norm(dim=-1, keepdim=True)
- return image_features.cpu().numpy().astype('float32')[0]
- except Exception as e:
- logger.error(f"Failed to process image. Reason: {str(e)}")
- return None
-
- def encode_image_from_url(self, url: str) -> Optional[np.ndarray]:
- """Complete pipeline: download, validate, preprocess and encode image from URL"""
- try:
- # Download image
- image_data = self.download_image(url)
-
- # Validate image
- image = self.validate_image(image_data)
-
- # Preprocess image
- image = self.preprocess_image(image)
-
- # Encode image
- embedding = self.encode_image(image)
-
- return embedding
-
- except Exception as e:
- logger.error(f"Error processing image from URL {url}: {str(e)}")
- return None
\ No newline at end of file
diff --git a/环境相关.md b/环境相关.md
deleted file mode 100644
index 841674a..0000000
--- a/环境相关.md
+++ /dev/null
@@ -1,123 +0,0 @@
-
-
-
-## 2. Python 运行环境
-
-```bash
-# 1. 激活 Conda
-source /home/tw/miniconda3/etc/profile.d/conda.sh
-conda activate searchengine
-
-# 如果部署到新机器,不存在 searchengine 环境时,需要初始化环境:
-cd /home/tw/SearchEngine
-pip install -r requirements.txt
-```
-
----
-
-## 3. 外部服务与端口
-
-| 服务 | 默认地址 | 说明 |
-|------|----------|------|
-| Elasticsearch | `http://localhost:9200` | 可通过 Docker 单节点启动 |
-| MySQL | `120.79.247.228:3316` | 存放店匠 SPU/SKU 数据 |
-| Redis(可选) | `localhost:6479` | Embedding/翻译缓存 |
-
-示例:使用 Docker 启动 Elasticsearch
-
-```bash
-docker run -d \
- --name elasticsearch \
- -p 9200:9200 \
- -e "discovery.type=single-node" \
- -e "ES_JAVA_OPTS=-Xms2g -Xmx2g" \
- elasticsearch:8.11.0
-```
-
----
-
-## 4. 环境变量与 `.env` 模板
-
-在项目根目录创建 `.env`,并根据环境替换敏感信息:
-
-```env
-# MySQL
-DB_HOST=120.79.247.228
-DB_PORT=3316
-DB_DATABASE=saas
-DB_USERNAME=saas
-DB_PASSWORD=P89cZHS5d7dFyc9R
-
-# Elasticsearch
-ES_HOST=http://localhost:9200
-ES_USERNAME=essa
-ES_PASSWORD=4hOaLaf41y2VuI8y
-
-# Redis(可选)
-REDIS_HOST=localhost
-REDIS_PORT=6479
-REDIS_PASSWORD=BMfv5aI31kgHWtlx
-
-# DeepL 翻译
-DEEPL_AUTH_KEY=c9293ab4-ad25-479b-919f-ab4e63b429ed
-
-# API
-API_HOST=0.0.0.0
-API_PORT=6002
-```
-
----
-
-## 5. 服务凭证速查
-
-| 项目 | 值 |
-|------|----|
-| **MySQL** | host `120.79.247.228`, port `3316`, user `saas`, password `P89cZHS5d7dFyc9R` |
-| **Elasticsearch** | host `http://localhost:9200`, user `essa`, password `4hOaLaf41y2VuI8y` |
-| **Redis(可选)** | host `localhost`, port `6479`, password `BMfv5aI31kgHWtlx` |
-| **DeepL** | `c9293ab4-ad25-479b-919f-ab4e63b429ed` |
-
-> 所有凭证仅用于本地/测试环境,生产环境需替换并妥善保管。
-
----
-
-## 6. 店匠数据源说明
-
-SearchEngine 以 MySQL 中的店匠标准表为权威数据源:
-
-- `shoplazza_product_spu`:SPU 商品主表
-- `shoplazza_product_sku`:SKU 变体表
-
-### `shoplazza_product_sku` 字段节选
-
-| 字段 | 类型 | 描述 |
-|------|------|------|
-| `id` | bigint(20) | SKU 主键 |
-| `spu_id` | bigint(20) | 对应 SPU |
-| `shop_id` | bigint(20) | 店铺 ID |
-| `shoplazza_product_id` | varchar(64) | 店匠商品 ID |
-| `title` | varchar(500) | 变体标题 |
-| `sku` | varchar(100) | SKU 编码 |
-| `price` | decimal(10,2) | 售价 |
-| `compare_at_price` | decimal(10,2) | 原价 |
-| `option1/2/3` | varchar(255) | 颜色/尺码等选项 |
-| `inventory_quantity` | int(11) | 库存 |
-| `image_src` | varchar(500) | 图片 |
-| `tenant_id` | bigint(20) | 租户 |
-| `create_time` | datetime | 创建时间 |
-| `update_time` | datetime | 更新时间 |
-| `deleted` | bit(1) | 逻辑删除标记 |
-
-> 完整字段、索引映射与 ES 对应关系详见 `INDEX_FIELDS_DOCUMENTATION.md`。
-
----
-
-## 7. 相关脚本
-
-- `scripts/mock_data.sh`:一次性生成 Tenant1 Mock + Tenant2 CSV 数据并导入 MySQL
-- `scripts/ingest.sh [recreate]`:从 MySQL 写入 Elasticsearch
-- `run.sh` / `restart.sh`:服务启动/重启
-
-更多脚本参数、日志与验证命令参见 `USAGE_GUIDE.md` 与 `TEST_DATA_GUIDE.md`。
-
-
diff --git a/设计文档.md b/设计文档.md
deleted file mode 100644
index df25f92..0000000
--- a/设计文档.md
+++ /dev/null
@@ -1,724 +0,0 @@
-# 搜索引擎通用化开发进度
-
-## 项目概述
-
-对后端搜索技术 做通用化。
-通用化的本质 是 对于各种业务数据、各种检索需求,都可以 用少量定制+配置化 来实现效果。
-
-
-**通用化的本质**:对于各种业务数据、各种检索需求,都可以用少量定制+配置化来实现效果。
-
----
-
-## 1. 原始数据层的约定
-
-### 1.1 店匠主表
-
-所有租户共用以下主表:
-- `shoplazza_product_sku` - SKU级别商品数据
-- `shoplazza_product_spu` - SPU级别商品数据
-
-### 1.2 索引结构(SPU维度)
-
-**统一索引架构**:
-- 所有客户共享同一个Elasticsearch索引:`search_products`
-- 索引粒度:SPU级别(每个文档代表一个SPU)
-- 数据隔离:通过`tenant_id`字段实现租户隔离
-- 嵌套结构:每个SPU文档包含嵌套的`variants`数组(SKU变体)
-
-**索引文档结构**:
-```json
-{
- "tenant_id": "1",
- "product_id": "123",
- "title": "蓝牙耳机",
- "variants": [
- {
- "variant_id": "456",
- "title": "黑色",
- "price": 199.99,
- "sku": "SKU-123-1",
- "stock": 50
- }
- ],
- "min_price": 199.99,
- "max_price": 299.99
-}
-```
-
-### 1.3 配置化方案
-
-**配置分离原则**:
-- **搜索配置**:只包含ES字段定义、查询域、排序规则等搜索相关配置
-- **数据源配置**:不在搜索配置中,由Pipeline层(脚本)决定
-- **数据导入流程**:写死的脚本,不依赖配置
-
-统一通过配置文件定义:
-1. ES 字段定义(字段类型、分析器、boost等)
-2. ES mapping 结构生成
-3. 查询域配置(indexes)
-4. 排序和打分配置(function_score)
-
-**注意**:配置中**不包含**以下内容:
-- `mysql_config` - MySQL数据库配置
-- `main_table` / `extension_table` - 数据表配置
-- `source_table` / `source_column` - 字段数据源映射
-
----
-
-## 2. 配置系统实现
-
-### 2.1 应用结构配置(字段定义)
-
-**配置文件位置**:`config/schema/{tenant_id}_config.yaml`
-
-**配置内容**:定义了 ES 的输入数据有哪些字段、关联 MySQL 的哪些字段。
-
-**实现情况**:
-
-#### 字段类型支持
-- **TEXT**:文本字段,支持多语言分析器
-- **KEYWORD**:关键词字段,用于精确匹配和聚合
-- **TEXT_EMBEDDING**:文本向量字段(1024维,dot_product相似度)
-- **IMAGE_EMBEDDING**:图片向量字段(1024维,dot_product相似度)
-- **INT/LONG**:整数类型
-- **FLOAT/DOUBLE**:浮点数类型
-- **DATE**:日期类型
-- **BOOLEAN**:布尔类型
-
-#### 分析器支持
-- **chinese_ecommerce**:中文电商分词器(index_ansj/query_ansj)
-- **english**:英文分析器
-- **russian**:俄文分析器
-- **arabic**:阿拉伯文分析器
-- **spanish**:西班牙文分析器
-- **japanese**:日文分析器
-- **standard**:标准分析器
-- **keyword**:关键词分析器
-
-#### 字段配置示例(Base配置)
-
-```yaml
-fields:
- # 租户隔离字段(必需)
- - name: "tenant_id"
- type: "KEYWORD"
- required: true
- index: true
- store: true
-
- # 商品标识字段
- - name: "product_id"
- type: "KEYWORD"
- required: true
- index: true
- store: true
-
- # 文本搜索字段
- - name: "title"
- type: "TEXT"
- analyzer: "chinese_ecommerce"
- boost: 3.0
- index: true
- store: true
-
- - name: "seo_keywords"
- type: "TEXT"
- analyzer: "chinese_ecommerce"
- boost: 2.0
- index: true
- store: true
-
- # 嵌套variants字段
- - name: "variants"
- type: "JSON"
- nested: true
- nested_properties:
- variant_id:
- type: "keyword"
- price:
- type: "float"
- sku:
- type: "keyword"
-```
-
-**注意**:配置中**不包含**`source_table`和`source_column`,数据源映射由Pipeline层决定。
-
-**实现模块**:
-- `config/config_loader.py` - 配置加载器
-- `config/field_types.py` - 字段类型定义
-- `indexer/mapping_generator.py` - ES mapping 生成器
-- `indexer/data_transformer.py` - 数据转换器
-
-### 2.2 索引结构配置(查询域配置)
-
-**配置内容**:定义了 ES 的字段索引 mapping 配置,支持各个域的查询,包括默认域的查询。
-
-**实现情况**:
-
-#### 域(Domain)配置
-每个域定义了:
-- 域名称(如 `default`, `title`, `category`, `brand`)
-- 域标签(中文描述)
-- 搜索字段列表
-- 默认分析器
-- 权重(boost)
-- **多语言字段映射**(`language_field_mapping`)
-
-#### 多语言字段映射
-
-支持将不同语言的查询路由到对应的字段:
-
-```yaml
-indexes:
- - name: "default"
- label: "默认索引"
- fields:
- - "name"
- - "enSpuName"
- - "ruSkuName"
- - "categoryName"
- - "brandName"
- analyzer: "chinese_ecommerce"
- boost: 1.0
- language_field_mapping:
- zh:
- - "name"
- - "categoryName"
- - "brandName"
- en:
- - "enSpuName"
- ru:
- - "ruSkuName"
-
- - name: "title"
- label: "标题索引"
- fields:
- - "name"
- - "enSpuName"
- - "ruSkuName"
- analyzer: "chinese_ecommerce"
- boost: 2.0
- language_field_mapping:
- zh:
- - "name"
- en:
- - "enSpuName"
- ru:
- - "ruSkuName"
-```
-
-**工作原理**:
-1. 检测查询语言(中文、英文、俄文等)
-2. 如果查询语言在 `language_field_mapping` 中,使用原始查询搜索对应语言的字段
-3. 将查询翻译到其他支持的语言,分别搜索对应语言的字段
-4. 组合多个语言查询的结果,提高召回率
-
-**实现模块**:
-- `search/multilang_query_builder.py` - 多语言查询构建器
-- `query/query_parser.py` - 查询解析器(支持语言检测和翻译)
-
----
-
-## 3. 数据导入流程
-
-### 3.1 数据源
-
-**店匠标准表**(Base配置使用):
-- `shoplazza_product_spu` - SPU级别商品数据
-- `shoplazza_product_sku` - SKU级别商品数据
-
-**其他客户表**(tenant1等):
-- 使用各自的数据源表和扩展表
-
-### 3.2 数据导入方式
-
-**Pipeline层决定数据源**:
-- 数据导入流程是写死的脚本,不依赖配置
-- 配置只关注ES搜索相关的内容
-- 数据源映射逻辑写死在转换器代码中
-
-#### Base配置数据导入(店匠通用)
-
-**脚本**:`scripts/ingest_shoplazza.py`
-
-**数据流程**:
-1. **数据加载**:从MySQL读取`shoplazza_product_spu`和`shoplazza_product_sku`表
-2. **数据转换**(`indexer/spu_transformer.py`):
- - 按`spu_id`和`tenant_id`关联SPU和SKU数据
- - 将SKU数据聚合为嵌套的`variants`数组
- - 计算扁平化价格字段(`min_price`, `max_price`, `compare_at_price`)
- - 字段映射(写死在代码中,不依赖配置)
- - 注入`tenant_id`字段
-3. **索引创建**:
- - 根据配置生成ES mapping
- - 创建或更新`search_products`索引
-4. **批量入库**:
- - 批量写入ES(默认每批500条)
- - 错误处理和重试机制
-
-**命令行工具**:
-```bash
-python scripts/ingest_shoplazza.py \
- --db-host localhost \
- --db-port 3306 \
- --db-database saas \
- --db-username root \
- --db-password password \
- --tenant-id "1" \
- --config base \
- --es-host http://localhost:9200 \
- --recreate \
- --batch-size 500
-```
-
-#### 其他客户数据导入
-
-- 使用各自的数据转换器(如`indexer/data_transformer.py`)
-- 数据源映射逻辑写死在各自的转换器中
-- 共享`search_products`索引,通过`tenant_id`隔离
-
-**实现模块**:
-- `indexer/spu_transformer.py` - SPU数据转换器(Base配置)
-- `indexer/data_transformer.py` - 通用数据转换器(其他客户)
-- `indexer/bulk_indexer.py` - 批量索引器
-- `scripts/ingest_shoplazza.py` - 店匠数据导入脚本
-
----
-
-## 4. QueryParser 实现
-
-
-### 4.1 查询改写(Query Rewriting)
-
-配置词典的key是query,value是改写后的查询表达式,比如。比如品牌词 改写为在brand|query OR name|query,类别词、标签词等都可以放进去。纠错、规范化、查询改写等 都可以通过这个词典来配置。
-**实现情况**:
-
-#### 配置方式
-在 `query_config.rewrite_dictionary` 中配置查询改写规则:
-
-```yaml
-query_config:
- enable_query_rewrite: true
- rewrite_dictionary:
- "芭比": "brand:芭比 OR name:芭比娃娃"
- "玩具": "category:玩具"
- "消防": "category:消防 OR name:消防"
-```
-
-#### 功能特性
-- **精确匹配**:查询完全匹配词典 key 时,替换为 value
-- **部分匹配**:查询包含词典 key 时,替换该部分
-- **支持布尔表达式**:value 可以是复杂的布尔表达式(AND, OR, 域查询等)
-
-#### 实现模块
-- `query/query_rewriter.py` - 查询改写器
-- `query/query_parser.py` - 查询解析器(集成改写功能)
-
-### 4.2 翻译(Translation)
-
-**实现情况**:
-
-#### 配置方式
-```yaml
-query_config:
- supported_languages:
- - "zh"
- - "en"
- - "ru"
- default_language: "zh"
- enable_translation: true
- translation_service: "deepl"
- translation_api_key: null # 通过环境变量设置
-```
-
-#### 功能特性
-1. **语言检测**:自动检测查询语言
-2. **智能翻译**:
- - 如果查询是中文,翻译为英文、俄文
- - 如果查询是英文,翻译为中文、俄文
- - 如果查询是其他语言,翻译为所有支持的语言
-3. **域感知翻译**:
- - 如果域有 `language_field_mapping`,只翻译到映射中存在的语言
- - 避免不必要的翻译,提高效率
-4. **翻译缓存**:缓存翻译结果,避免重复调用 API
-
-#### 工作流程
-```
-查询输入 → 语言检测 → 确定目标语言 → 翻译 → 多语言查询构建
-```
-
-#### 实现模块
-- `query/language_detector.py` - 语言检测器
-- `query/translator.py` - 翻译器(DeepL API)
-- `query/query_parser.py` - 查询解析器(集成翻译功能)
-
-### 4.3 文本向量化(Text Embedding)
-
-如果配置打开了text_embedding查询,并且query 包含了default域的查询,那么要把default域的查询词转向量,后面searcher会用这个向量参与查询。
-
-**实现情况**:
-
-#### 配置方式
-```yaml
-query_config:
- enable_text_embedding: true
-```
-
-#### 功能特性
-1. **条件生成**:
- - 仅当 `enable_text_embedding=true` 时生成向量
- - 仅对 `default` 域查询生成向量
-2. **向量模型**:BGE-M3 模型(1024维向量)
-3. **用途**:用于语义搜索(KNN 检索)
-
-#### 实现模块
-- `embeddings/bge_encoder.py` - BGE 文本编码器
-- `query/query_parser.py` - 查询解析器(集成向量生成)
-
----
-
-## 5. Searcher 实现
-
-参考opensearch,他们自己定义的一套索引结构配置、支持自定义的一套检索表达式、排序表达式,这是各个客户进行配置化的基础,包括索引结构配置、排序策略配置。
-比如各种业务过滤策略 可以简单的通过表达式满足,比如brand|耐克 AND cate2|xxx。指定字段排序可以通过排序的表达式实现。
-
-查询默认在default域,相也会对这个域的查询做一些相关性的重点优化,包括融合语义相关性、多语言相关性(可以基于配置 将查询翻译到指定语言并在对应的语言的字段进行查询)来弥补传统查询分析手段(比如查询改写 纠错 词权重等)的不足,也支持通过配置一些词表转为泛查询模式来优化相关性。
-
-### 5.1 布尔表达式解析
-
-**实现情况**:
-
-#### 支持的运算符
-- **AND**:所有项必须匹配
-- **OR**:任意项匹配
-- **RANK**:排序增强(类似 OR 但影响排序)
-- **ANDNOT**:排除(第一项匹配,第二项不匹配)
-- **()**:括号分组
-
-#### 优先级(从高到低)
-1. `()` - 括号
-2. `ANDNOT` - 排除
-3. `AND` - 与
-4. `OR` - 或
-5. `RANK` - 排序
-
-#### 示例
-```
-laptop AND (gaming OR professional) ANDNOT cheap
-```
-
-#### 实现模块
-- `search/boolean_parser.py` - 布尔表达式解析器
-- `search/searcher.py` - 搜索器(集成布尔解析)
-
-### 5.2 多语言搜索
-
-**实现情况**:
-
-#### 工作原理
-1. **查询解析**:
- - 提取域(如 `title:查询` → 域=`title`,查询=`查询`)
- - 检测查询语言
- - 生成翻译
-2. **多语言查询构建**:
- - 如果域有 `language_field_mapping`:
- - 使用检测到的语言查询对应字段(boost * 1.5)
- - 使用翻译后的查询搜索其他语言字段(boost * 1.0)
- - 如果域没有 `language_field_mapping`:
- - 使用所有字段进行搜索
-3. **查询组合**:
- - 多个语言查询组合为 `should` 子句
- - 提高召回率
-
-#### 示例
-```
-查询: "芭比娃娃"
-域: default
-检测语言: zh
-
-生成的查询:
-- 中文查询 "芭比娃娃" → 搜索 name, categoryName, brandName (boost * 1.5)
-- 英文翻译 "Barbie doll" → 搜索 enSpuName (boost * 1.0)
-- 俄文翻译 "Кукла Барби" → 搜索 ruSkuName (boost * 1.0)
-```
-
-#### 实现模块
-- `search/multilang_query_builder.py` - 多语言查询构建器
-- `search/searcher.py` - 搜索器(使用多语言构建器)
-
-### 5.3 相关性计算(Ranking)
-
-**实现情况**:
-
-#### 当前实现
-**公式**:`bm25() + 0.2 * text_embedding_relevance()`
-
-- **bm25()**:BM25 文本相关性得分
- - 包括多语言打分
- - 内部通过配置翻译为多种语言
- - 分别到对应的字段搜索
- - 中文字段使用中文分词器,英文字段使用英文分词器
-- **text_embedding_relevance()**:文本向量相关性得分(KNN 检索的打分)
- - 权重:0.2
-
-#### 配置方式
-```yaml
-ranking:
- expression: "bm25() + 0.2*text_embedding_relevance()"
- description: "BM25 text relevance combined with semantic embedding similarity"
-```
-
-#### 扩展性
-- 支持表达式配置(未来可扩展)
-- 支持自定义函数(如 `timeliness()`, `field_value()`)
-
-#### 实现模块
-- `search/ranking_engine.py` - 排序引擎
-- `search/searcher.py` - 搜索器(集成排序功能)
-
----
-
-## 6. 已完成功能总结
-
-### 6.1 配置系统
-- ✅ 字段定义配置(类型、分析器、来源表/列)
-- ✅ 索引域配置(多域查询、多语言映射)
-- ✅ 查询配置(改写词典、翻译配置)
-- ✅ 排序配置(表达式配置)
-- ✅ 配置验证(字段存在性、类型检查、分析器匹配)
-
-### 6.2 数据索引
-- ✅ 数据转换(字段映射、类型转换)
-- ✅ 向量生成(文本向量、图片向量)
-- ✅ 向量缓存(避免重复计算)
-- ✅ 批量索引(错误处理、重试机制)
-- ✅ ES mapping 自动生成
-
-### 6.3 查询处理
-- ✅ 查询改写(词典配置)
-- ✅ 语言检测
-- ✅ 多语言翻译(DeepL API)
-- ✅ 文本向量化(BGE-M3)
-- ✅ 域提取(支持 `domain:query` 语法)
-
-### 6.4 搜索功能
-- ✅ 布尔表达式解析(AND, OR, RANK, ANDNOT, 括号)
-- ✅ 多语言查询构建(语言路由、字段映射)
-- ✅ 语义搜索(KNN 检索)
-- ✅ 相关性排序(BM25 + 向量相似度)
-- ✅ 结果聚合(Faceted Search)
-
-### 6.5 API 服务
-- ✅ RESTful API(FastAPI)
-- ✅ 搜索接口(文本搜索、图片搜索)
-- ✅ 文档查询接口
-- ✅ 前端界面(HTML + JavaScript)
-- ✅ 租户隔离(tenant_id过滤)
-
-### 6.6 Base配置(店匠通用)
-- ✅ SPU级别索引结构
-- ✅ 嵌套variants字段
-- ✅ 统一索引(search_products)
-- ✅ 租户隔离(tenant_id)
-- ✅ 配置简化(移除MySQL相关配置)
-
----
-
-## 7. 技术栈
-
-- **后端**:Python 3.6+
-- **搜索引擎**:Elasticsearch
-- **数据库**:MySQL(Shoplazza)
-- **向量模型**:BGE-M3(文本)、CN-CLIP(图片)
-- **翻译服务**:DeepL API
-- **API 框架**:FastAPI
-- **前端**:HTML + JavaScript
-
----
-
-## 8. API响应格式
-
-### 8.1 外部友好格式
-
-API返回格式不包含ES内部字段(`_id`, `_score`, `_source`),使用外部友好的格式:
-
-**响应结构**:
-```json
-{
- "results": [
- {
- "product_id": "123",
- "title": "蓝牙耳机",
- "variants": [
- {
- "variant_id": "456",
- "price": 199.99,
- "sku": "SKU-123-1",
- "stock": 50
- }
- ],
- "relevance_score": 0.95
- }
- ],
- "total": 10,
- "facets": [...],
- "suggestions": [],
- "related_searches": []
-}
-```
-
-**主要变化**:
-- 结构化结果(`ProductResult`和`VariantResult`)
-- 嵌套variants数组
-- 无ES内部字段
-
-### 8.2 租户隔离
-
-所有API请求必须提供`tenant_id`:
-- 请求头:`X-Tenant-ID: 1`
-- 或查询参数:`?tenant_id=1`
-
-搜索时自动添加`tenant_id`过滤,确保数据隔离。
-
-### 8.3 数据接口约定
-
-**统一的数据约定格式**:所有API接口使用 Pydantic 模型进行数据验证和序列化。
-
-#### 8.3.1 数据流模式
-
-系统采用统一的数据流模式,确保数据在各层之间的一致性:
-
-**数据流转路径**:
-```
-API Request (JSON)
- ↓
-Pydantic 验证 → 结构化模型(RangeFilter, FacetConfig 等)
- ↓
-Searcher(透传)
- ↓
-ES Query Builder → model_dump() 转换为字典
- ↓
-ES Query (字典)
- ↓
-Elasticsearch
-```
-
-#### 8.3.2 Facets 配置数据流
-
-**输入格式**:`List[Union[str, FacetConfig]]`
-
-- **简单模式**:字符串列表(字段名),使用默认配置
- ```json
- ["categoryName_keyword", "brandName_keyword"]
- ```
-
-- **高级模式**:FacetConfig 对象列表,支持自定义配置
- ```json
- [
- {
- "field": "categoryName_keyword",
- "size": 15,
- "type": "terms"
- },
- {
- "field": "price",
- "type": "range",
- "ranges": [
- {"key": "0-50", "to": 50},
- {"key": "50-100", "from": 50, "to": 100}
- ]
- }
- ]
- ```
-
-**数据流**:
-1. API 层:接收 `List[Union[str, FacetConfig]]`
-2. Searcher 层:透传,不做转换
-3. ES Query Builder:只接受 `str` 或 `FacetConfig`,自动处理两种格式
-4. 输出:转换为 ES 聚合查询
-
-#### 8.3.3 Range Filters 数据流
-
-**输入格式**:`Dict[str, RangeFilter]`
-
-**RangeFilter 模型**:
-```python
-class RangeFilter(BaseModel):
- gte: Optional[Union[float, str]] # 大于等于
- gt: Optional[Union[float, str]] # 大于
- lte: Optional[Union[float, str]] # 小于等于
- lt: Optional[Union[float, str]] # 小于
-```
-
-**示例**:
-```json
-{
- "price": {"gte": 50, "lte": 200},
- "created_at": {"gte": "2023-01-01T00:00:00Z"}
-}
-```
-
-**数据流**:
-1. API 层:接收 `Dict[str, RangeFilter]`,Pydantic 自动验证
-2. Searcher 层:透传 `Dict[str, RangeFilter]`
-3. ES Query Builder:调用 `range_filter.model_dump()` 转换为字典
-4. 输出:ES range 查询(支持数值和日期)
-
-**特性**:
-- 自动验证:确保至少指定一个边界值(gte, gt, lte, lt)
-- 类型支持:支持数值(float)和日期时间字符串(ISO 格式)
-- 统一约定:所有范围过滤都使用 RangeFilter 模型
-
-#### 8.3.4 响应 Facets 数据流
-
-**输出格式**:`List[FacetResult]`
-
-**FacetResult 模型**:
-```python
-class FacetResult(BaseModel):
- field: str # 字段名
- label: str # 显示标签
- type: Literal["terms", "range"] # 分面类型
- values: List[FacetValue] # 分面值列表
- total_count: Optional[int] # 总文档数
-```
-
-**数据流**:
-1. ES Response:返回聚合结果(字典格式)
-2. Searcher 层:构建 `List[FacetResult]` 对象
-3. API 层:直接返回 `List[FacetResult]`(Pydantic 自动序列化为 JSON)
-
-**优势**:
-- 类型安全:使用 Pydantic 模型确保数据结构一致性
-- 自动序列化:模型自动转换为 JSON,无需手动处理
-- 统一约定:所有响应都使用标准化的 Pydantic 模型
-
-#### 8.3.5 统一约定的好处
-
-1. **类型安全**:使用 Pydantic 模型提供运行时类型检查和验证
-2. **代码一致性**:所有层使用相同的数据模型,减少转换错误
-3. **自动文档**:FastAPI 自动生成 API 文档(基于 Pydantic 模型)
-4. **易于维护**:修改数据结构只需更新模型定义
-5. **数据验证**:自动验证输入数据,减少错误处理代码
-
-**实现模块**:
-- `api/models.py` - 所有 Pydantic 模型定义
-- `api/result_formatter.py` - 结果格式化器(ES 响应 → Pydantic 模型)
-- `search/es_query_builder.py` - ES 查询构建器(Pydantic 模型 → ES 查询)
-
-## 9. 配置文件示例
-
-**Base配置**(店匠通用):`config/schema/base/config.yaml`
-
-**其他客户配置**:`config/schema/tenant1/config.yaml`
-
----
-
-## 9. 相关文档
-
-- `MULTILANG_FEATURE.md` - 多语言功能详细说明
-- `QUICKSTART.md` - 快速开始指南
-- `HighLevelDesign.md` - 高层设计文档
-- `IMPLEMENTATION_SUMMARY.md` - 实现总结
-- `商品数据源入ES配置规范.md` - 数据源配置规范
diff --git a/阿里opensearch电商行业.md b/阿里opensearch电商行业.md
deleted file mode 100644
index 2e54e03..0000000
--- a/阿里opensearch电商行业.md
+++ /dev/null
@@ -1,47 +0,0 @@
-https://help.aliyun.com/zh/open-search/industry-algorithm-edition/e-commerce?spm=a2c4g.11186623.help-menu-29102.d_3_2_1.5a903cfbxOsaHt&scm=20140722.H_99739._.OR_help-T_cn~zh-V_1
-
-
-## 定义应用结构
-示例如下:
-| 字段名称 | 主键 | 字段标签 | 类型 |
-|----------------|------|------------|--------------|
-| title | | 商品标题 | TEXT |
-| text_embedding | | 文本向量 | EMBEDDING |
-| image_embedding | | 图片向量 | EMBEDDING |
-| category_name | | 类目名称 | TEXT |
-| image_url | | | LITERAL_ARRAY|
-| description | | 商品描述 | TEXT |
-| brand_name | | 品牌名称 | TEXT |
-| thumbnail_url | | | LITERAL_ARRAY|
-| is_onsale | | | INT |
-| url | | | LITERAL |
-| brand_id | | | LITERAL |
-| series_id | | | LITERAL |
-| sold_num | | 商品销量 | INT |
-| category_id | | | INT |
-| onsale_time | | 上架时间 | INT |
-| price | | | DOUBLE |
-| series_name | | | TEXT |
-| discount_price | | DOUBLE |
-| pid | ● | INT |
-| sale_price | | DOUBLE |
-| act_price | | DOUBLE |
-
-
-## 定义索引结构
-
-| 索引名称 | 索引标签 | 包含字段 | 分析方式 | 使用示例 |
-| --- | --- | --- | --- | --- |
-| default | 默认索引 | category_name, description, brand_name, title, create_by, update_by | 行业 - 电商通用分析 | query=default:“云搜索” |
-| category_name | 类目名称索引 | category_name | 行业 - 电商通用分析 | query=category_name:“云搜索” |
-| category_id | | category_id | 关键字 | query=category_id:“云搜索” |
-| series_name | | series_name | 中文 - 通用分析 | query=series_name:“云搜索” |
-| brand_name | | brand_name | 中文 - 通用分析 | query=brand_name:“云搜索” |
-| id | | id | 关键字 | query=id:“云搜索” |
-| title | 标题索引 | title | 行业 - 电商通用分析 | query=title:“云搜索” |
-| seller_id | | seller_id | 关键字 | query=seller_id:“云搜索” |
-| brand_id | | brand_id | 关键字 | query=brand_id:“云搜索” |
-| series_id | | series_id | 关键字 | query=series_id:“云搜索” |
-
-上面的只是阿里云的opensearch的例子,我们也要有同样的一套配置,这里支持的“字分析方式” 为ES预先支持的 多种分析器,我们要支持的分析方式参考 @商品数据源入ES配置规范.md
-
--
libgit2 0.21.2