搜索引擎 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/
描述: 执行文本搜索查询,支持多语言、布尔表达式、过滤器和分面搜索。
请求参数
{
"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 逻辑)。
格式:
{
"filters": {
"categoryName_keyword": "玩具", // 单值:精确匹配
"brandName_keyword": ["乐高", "孩之宝"], // 数组:匹配任意值(OR)
"in_stock": true // 布尔值
}
}
支持的值类型:
- 字符串:精确匹配
- 整数:精确匹配
- 布尔值:精确匹配
- 数组:匹配任意值(OR 逻辑)
范围过滤器 (range_filters)
用于数值字段的范围过滤。
格式:
{
"range_filters": {
"price": {
"gte": 50, // 大于等于
"lte": 200 // 小于等于
},
"days_since_last_update": {
"lte": 30 // 最近30天更新
}
}
}
支持的操作符:
gte: 大于等于 (>=)gt: 大于 (>)lte: 小于等于 (lt: 小于 (
注意: 至少需要指定一个操作符。
分面配置 (facets)
用于生成分面统计(分组聚合)。
简单模式(字符串数组):
{
"facets": ["categoryName_keyword", "brandName_keyword"]
}
高级模式(配置对象数组):
{
"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' 时需要)
响应格式
{
"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: 简单搜索
curl -X POST "http://localhost:6002/search/" \
-H "Content-Type: application/json" \
-d '{
"query": "芭比娃娃",
"size": 20
}'
示例 2: 带过滤器的搜索
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: 带分面搜索(简单模式)
curl -X POST "http://localhost:6002/search/" \
-H "Content-Type: application/json" \
-d '{
"query": "玩具",
"size": 20,
"facets": ["categoryName_keyword", "brandName_keyword"]
}'
示例 4: 带分面搜索(高级模式)
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: 复杂搜索(布尔表达式+过滤+排序)
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": "price",
"sort_order": "asc",
"debug": false
}'
2. 图片搜索
端点: POST /search/image
描述: 基于图片相似度进行搜索,使用图片向量进行语义匹配。
请求参数
{
"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 包含图片信息:
{
"hits": [...],
"total": 50,
"max_score": 0.95,
"took_ms": 120,
"query_info": {
"image_url": "https://example.com/image.jpg",
"search_type": "image_similarity"
}
}
请求示例
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 |
响应格式
{
"query": "芭",
"suggestions": [
{
"text": "芭比娃娃",
"type": "query",
"highlight": "<em>芭</em>比娃娃",
"popularity": 850
}
],
"took_ms": 5
}
请求示例
curl "http://localhost:6002/search/suggestions?q=芭&size=5&types=query,product"
4. 即时搜索(框架)
端点: GET /search/instant
描述: 即时搜索,边输入边搜索。
注意: 此功能暂未实现,调用标准搜索接口。
查询参数
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
q |
string | ✅ | - | 搜索查询(最少2个字符) |
size |
integer | ❌ | 5 | 结果数量(1-20) |
请求示例
curl "http://localhost:6002/search/instant?q=玩具&size=5"
5. 获取单个文档
端点: GET /search/{doc_id}
描述: 根据文档ID获取单个文档详情。
路径参数
| 参数 | 类型 | 描述 |
|---|---|---|
doc_id |
string | 文档ID |
响应格式
{
"id": "12345",
"source": {
"name": "芭比时尚娃娃",
"price": 89.99,
"categoryName": "玩具"
}
}
请求示例
curl "http://localhost:6002/search/12345"
管理接口
1. 健康检查
端点: GET /admin/health
描述: 检查服务健康状态。
响应格式
{
"status": "healthy",
"elasticsearch": "connected",
"customer_id": "customer1"
}
2. 获取配置
端点: GET /admin/config
描述: 获取当前客户配置(脱敏)。
响应格式
{
"customer_id": "customer1",
"customer_name": "Customer1 Test Instance",
"es_index_name": "search_customer1",
"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
描述: 获取索引统计信息。
响应格式
{
"index_name": "search_customer1",
"document_count": 10000,
"size_mb": 523.45
}
4. 查询改写规则
端点: GET /admin/rewrite-rules
描述: 获取当前的查询改写规则。
响应格式
{
"rules": {
"乐高": "brand:乐高 OR name:乐高",
"玩具": "category:玩具"
},
"count": 2
}
端点: POST /admin/rewrite-rules
描述: 更新查询改写规则。
请求格式
{
"乐高": "brand:乐高 OR name:乐高",
"芭比": "brand:芭比 OR name:芭比"
}
使用示例
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": "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 示例
// 搜索函数
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 示例
# 简单搜索
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": "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 芭比) |
操作符优先级
从高到低:
()- 括号ANDNOT- 排除AND- 与OR- 或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 关系(必须同时满足)。
{
"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 参数:
{
"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参数(不向后兼容)
迁移指南:
旧接口:
{
"filters": {
"price_ranges": ["0-50", "50-100"]
},
"aggregations": {
"category_stats": {"terms": {"field": "categoryName_keyword", "size": 15}}
}
}
新接口:
{
"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维) |