README.md 14.1 KB

电商搜索引擎 SaaS 系统

一个可配置的多租户搜索引擎,专为跨境电商独立站(店匠 Shoplazza)设计。

系统特性

  • 多语言支持:支持中文、英文、俄文、阿拉伯文、西班牙文、日文,支持自动翻译
  • 语义搜索:基于 BGE-M3 文本向量和 CN-CLIP 图片向量的语义检索
  • 混合排序:结合 BM25 文本相关性和语义相似度
  • 布尔表达式:支持 AND、OR、RANK、ANDNOT 操作符,支持括号优先级
  • 灵活过滤:精确匹配过滤器和数值范围过滤器
  • 分面搜索:动态生成过滤选项,提供分组统计
  • 可配置化:客户特定的字段定义、分析器、排序表达式
  • 多租户隔离:通过 tenant_id 实现数据隔离,共享统一索引
  • RESTful API:基于 FastAPI 的完整 API 服务
  • 前端界面:提供可视化搜索测试界面

系统架构

数据模型

  • SPU 级别索引:所有租户共享 search_products 索引
  • 嵌套结构:每个 SPU 文档包含嵌套的 variants 数组(SKU 变体)
  • 租户隔离:通过 tenant_id 字段实现多租户数据隔离
  • 数据源:MySQL 数据库(shoplazza_product_spushoplazza_product_sku 表)

技术栈

  • 后端框架:Python 3.8+ / FastAPI
  • 搜索引擎:Elasticsearch 8.x
  • 数据库:MySQL(店匠数据表)
  • 向量模型
    • 文本向量:BGE-M3(1024维)
    • 图片向量:CN-CLIP(1024维)
  • 翻译服务:DeepL API
  • 前端:HTML + JavaScript

快速开始

1. 环境准备

安装依赖

pip install -r requirements.txt

启动 Elasticsearch

# 使用 Docker
docker run -d \
  --name elasticsearch \
  -p 9200:9200 \
  -e "discovery.type=single-node" \
  -e "ES_JAVA_OPTS=-Xms2g -Xmx2g" \
  elasticsearch:8.11.0

配置环境变量(可选)

创建 .env 文件:

DB_HOST=120.79.247.228
DB_PORT=3316
DB_DATABASE=saas
DB_USERNAME=saas
DB_PASSWORD=your_password
ES_HOST=http://localhost:9200

2. 脚本体系

项目提供统一的脚本系统,管理完整的工作流程:

脚本说明

脚本 功能 说明
restart.sh 重启服务 停止并重新启动前后端服务
run.sh 启动服务 启动前端和后端服务
scripts/mock_data.sh 数据导入 将 Mock 数据或 CSV 数据导入 MySQL
scripts/ingest.sh 数据索引 从 MySQL 导入数据到 Elasticsearch

3. 手动启动 API 服务(可选)

如果不想使用脚本,可以手动启动:

python -m api.app \
  --host 0.0.0.0 \
  --port 6002 \
  --es-host http://localhost:9200 \
  --reload

4. 测试搜索

简单搜索

curl -X POST http://localhost:6002/search/ \
  -H "Content-Type: application/json" \
  -d '{
    "query": "蓝牙耳机",
    "size": 10
  }'

带过滤器的搜索

curl -X POST http://localhost:6002/search/ \
  -H "Content-Type: application/json" \
  -d '{
    "query": "玩具",
    "size": 10,
    "filters": {
      "categoryName_keyword": "玩具"
    },
    "range_filters": {
      "min_price": {
        "gte": 50,
        "lte": 200
      }
    }
  }'

布尔表达式搜索

curl -X POST http://localhost:6002/search/ \
  -H "Content-Type: application/json" \
  -d '{
    "query": "蓝牙 AND (耳机 OR 音响)",
    "size": 10
  }'

图片搜索

curl -X POST http://localhost:6002/search/image \
  -H "Content-Type: application/json" \
  -d '{
    "image_url": "https://oss.essa.cn/example.jpg",
    "size": 10
  }'

分面搜索

curl -X POST http://localhost:6002/search/ \
  -H "Content-Type: application/json" \
  -d '{
    "query": "玩具",
    "size": 10,
    "facets": [
      {
        "field": "categoryName_keyword",
        "label": "分类"
      },
      {
        "field": "brandName_keyword",
        "label": "品牌"
      }
    ]
  }'

项目结构

SearchEngine/
├── api/                      # API 服务
│   ├── app.py               # FastAPI 应用主入口
│   ├── models.py            # 请求/响应模型
│   └── routes/              # API 路由
│       ├── search.py        # 搜索接口
│       └── admin.py         # 管理接口
├── config/                   # 配置系统
│   ├── field_types.py       # 字段类型定义
│   ├── config_loader.py     # 配置加载器
│   └── schema/              # 租户配置文件
│       └── base/            # Base 配置(店匠通用)
│           └── config.yaml
├── indexer/                  # 数据索引
│   ├── mapping_generator.py # ES mapping 生成器
│   ├── spu_transformer.py   # SPU 数据转换器
│   ├── bulk_indexer.py      # 批量索引器
│   └── ingest_shoplazza.py  # 店匠数据导入脚本
├── query/                    # 查询处理
│   ├── query_parser.py      # 查询解析器
│   ├── language_detector.py # 语言检测
│   ├── translator.py        # 翻译服务
│   └── query_rewriter.py    # 查询改写
├── search/                   # 搜索执行
│   ├── searcher.py          # 主搜索器
│   ├── multilang_query_builder.py  # 多语言查询构建器
│   ├── boolean_parser.py    # 布尔表达式解析器
│   ├── es_query_builder.py   # ES 查询构建器
│   └── ranking_engine.py    # 排序引擎
├── embeddings/               # 向量编码
│   ├── text_encoder.py      # BGE-M3 文本编码器
│   └── image_encoder.py     # CN-CLIP 图片编码器
├── utils/                    # 工具类
│   ├── db_connector.py      # MySQL 连接器
│   ├── es_client.py         # ES 客户端封装
│   └── cache.py             # 向量缓存
├── scripts/                  # 脚本工具
│   ├── mock_data.sh         # Mock 数据导入脚本
│   ├── ingest.sh            # 数据索引脚本
│   ├── generate_test_data.py # 生成测试数据
│   ├── import_tenant2_csv.py # Tenant2 CSV 导入脚本
│   └── import_test_data.py  # 数据导入脚本
├── frontend/                 # 前端界面
│   └── unified.html         # 统一搜索界面
├── data/                     # 数据文件
│   └── customer1/           # customer1 测试数据
├── run.sh                    # 启动脚本
├── restart.sh                # 重启脚本
└── requirements.txt         # Python 依赖

配置系统

配置文件结构

配置文件位于 config/schema/{tenant_id}/config.yaml,Base 配置位于 config/schema/base/config.yaml

配置内容

1. 字段定义 (fields)

定义 ES 索引的字段结构:

fields:
  title_zh:
    type: TEXT
    analyzer: chinese_ecommerce
    index: true
    store: true
    boost: 2.0
  title_en:
    type: TEXT
    analyzer: english
    index: true
    store: true
  categoryName_keyword:
    type: KEYWORD
    index: true
    store: true
  min_price:
    type: FLOAT
    index: true
    store: true
  title_embedding:
    type: TEXT_EMBEDDING
    dimension: 1024
    similarity: dot_product

2. 查询域配置 (indexes)

定义多域查询配置:

indexes:
  default:
    fields: ["title_zh", "title_en", "title_ru"]
    weights: {"title_zh": 2.0, "title_en": 1.5}
  title:
    fields: ["title_zh", "title_en"]
  brand:
    fields: ["brandName_keyword"]

3. 查询配置 (query_config)

多语言和翻译配置:

query_config:
  languages: ["zh", "en", "ru"]
  auto_translate: true
  translation_api: "deepl"
  enable_embeddings: true
  embedding_model: "bge-m3"

4. 排序配置 (ranking)

相关性排序表达式:

ranking:
  expression: "bm25() + 0.2*text_embedding_relevance() + general_score*2"
  enable_function_score: true

5. SPU 配置 (spu_config)

SPU 聚合配置:

spu_config:
  enabled: true
  spu_field: "product_id"
  inner_hits_size: 3

字段类型

类型 说明 示例
TEXT 文本字段,支持分词 商品标题、描述
KEYWORD 关键词字段,精确匹配 分类、品牌
TEXT_EMBEDDING 文本向量(1024维) 语义搜索
IMAGE_EMBEDDING 图片向量(1024维) 图片搜索
INT/LONG 整数类型 库存、ID
FLOAT/DOUBLE 浮点数类型 价格
DATE 日期类型 创建时间
BOOLEAN 布尔类型 是否上架

分析器

分析器 说明 适用语言
chinese_ecommerce 中文电商分词器(Ansj) 中文
english 英文分析器 英文
russian 俄文分析器 俄文
arabic 阿拉伯文分析器 阿拉伯文
spanish 西班牙文分析器 西班牙文
japanese 日文分析器 日文
standard 标准分析器 通用
keyword 关键词分析器 精确匹配

API 接口

搜索接口

1. 文本搜索

端点: POST /search/

请求示例:

{
  "query": "蓝牙耳机",
  "size": 10,
  "from": 0,
  "filters": {
    "categoryName_keyword": "电子产品"
  },
  "range_filters": {
    "min_price": {
      "gte": 50,
      "lte": 500
    }
  },
  "facets": [
    {
      "field": "categoryName_keyword",
      "label": "分类"
    }
  ],
  "sort_by": "min_price",
  "sort_order": "asc"
}

2. 图片搜索

端点: POST /search/image

请求示例:

{
  "image_url": "https://oss.essa.cn/example.jpg",
  "size": 10,
  "filters": {
    "categoryName_keyword": "玩具"
  }
}

3. 获取文档

端点: GET /search/{doc_id}

管理接口

1. 健康检查

端点: GET /admin/health

2. 获取配置

端点: GET /admin/config?tenant_id=1

3. 索引统计

端点: GET /admin/stats?tenant_id=1

4. 查询改写规则

端点:

  • GET /admin/rewrite-rules?tenant_id=1 - 获取规则
  • POST /admin/rewrite-rules - 更新规则

详细 API 文档请参考 API_DOCUMENTATION.md

高级功能

布尔表达式

支持的操作符(优先级从高到低):

  1. () - 括号
  2. ANDNOT - 排除
  3. AND - 必须全部匹配
  4. OR - 任意匹配
  5. RANK - 排序提升

示例:

蓝牙 AND (耳机 OR 音响) ANDNOT 便宜
laptop AND (gaming OR professional) ANDNOT cheap

查询改写

配置品牌/分类映射:

rewrite_dictionary:
  "苹果": "brand:苹果 OR name:iPhone"
  "玩具": "category:玩具"

排序表达式

可配置的相关性排序:

bm25() + 0.2*text_embedding_relevance() + general_score*2 + timeliness(end_time)

支持的函数:

  • bm25() - BM25 相关性分数
  • text_embedding_relevance() - 文本向量相似度
  • image_embedding_relevance() - 图片向量相似度
  • field_value(field_name) - 字段值
  • timeliness(date_field) - 时间衰减

多语言查询

系统自动检测查询语言,并支持:

  • 自动翻译:将查询翻译到配置的所有语言
  • 语言路由:根据语言选择对应的字段
  • 混合查询:同时查询多个语言字段

SPU 聚合

启用 SPU 聚合后,每个 SPU 只返回一个代表性 SKU:

spu_config:
  enabled: true
  spu_field: "product_id"
  inner_hits_size: 3  # 每个 SPU 内部返回的 SKU 数量

数据导入

MySQL 表结构

系统使用店匠的标准表结构:

  • SPU 表: shoplazza_product_spu
  • SKU 表: shoplazza_product_sku

数据导入流程

  1. 生成 SQL:使用 scripts/generate_test_data.pyscripts/import_tenant2_csv.py 生成 SQL 文件
  2. 导入 MySQL:使用 scripts/mock_data.shscripts/import_test_data.py 导入数据
  3. 索引到 ES:使用 scripts/ingest.shscripts/ingest_shoplazza.py 将数据索引到 Elasticsearch

CSV 数据格式

CSV 文件应包含以下字段:

  • skuId - SKU ID
  • name - 商品名称(中文)
  • name_pinyin - 拼音
  • create_time - 创建时间
  • ruSkuName - 俄文名称
  • enSpuName - 英文名称
  • categoryName - 分类名称
  • supplierName - 供应商名称
  • brandName - 品牌名称
  • file_id - 文件 ID
  • id - 商品 ID
  • imageUrl - 图片 URL

性能优化

1. 向量缓存

启用向量缓存可以避免重复计算:

# 在配置中启用缓存
cache:
  enabled: true
  ttl: 3600  # 缓存过期时间(秒)

2. 批量处理

调整批量大小以优化性能:

  • 数据转换批量大小:默认 100(根据内存调整)
  • 索引批量大小:默认 500(根据 ES 性能调整)

3. GPU 加速

使用 GPU 加速向量计算:

# 安装 CUDA 版本的 PyTorch
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118

4. ES 分片配置

根据数据量配置 ES 分片:

# 在 mapping 生成器中配置
settings:
  number_of_shards: 3
  number_of_replicas: 1

开发指南

运行测试

pytest tests/

代码格式化

black .

类型检查

mypy .

日志查看

# 查看服务日志
tail -f logs/search_service.log

常见问题

1. 如何添加新的租户?

  1. 创建配置文件 config/schema/{tenant_id}/config.yaml
  2. 导入数据到 MySQL(使用 scripts/mock_data.sh
  3. 索引数据到 ES(使用 scripts/ingest.sh {tenant_id}

2. 如何修改排序规则?

编辑配置文件中的 ranking.expression 字段。

3. 如何添加新的分析器?

config/field_types.py 中定义新的分析器,然后在字段配置中使用。

4. 向量计算很慢怎么办?

  • 启用向量缓存
  • 使用 GPU 加速
  • 减少批量大小

5. 如何调试搜索查询?

在 API 请求中设置 debug: true,返回详细的调试信息。

相关文档

  • API 文档: API_DOCUMENTATION.md
  • 设计文档: 设计文档.md
  • 部署指南: DEPLOYMENT.md
  • 用户指南: USER_GUIDE.md

许可证

专有软件 - 保留所有权利