Name Last Update
.cursor/plans Loading commit data...
.github/workflows Loading commit data...
api Loading commit data...
config Loading commit data...
context Loading commit data...
data/customer1 Loading commit data...
docs Loading commit data...
embeddings Loading commit data...
frontend Loading commit data...
indexer Loading commit data...
query Loading commit data...
scripts Loading commit data...
search Loading commit data...
tests Loading commit data...
utils Loading commit data...
.env Loading commit data...
.env.example Loading commit data...
.gitignore Loading commit data...
API_DOCUMENTATION.md Loading commit data...
API_EXAMPLES.md Loading commit data...
API_INTEGRATION_GUIDE.md Loading commit data...
API_QUICK_REFERENCE.md Loading commit data...
BEST_PRACTICES_REFACTORING.md Loading commit data...
CHANGES.md Loading commit data...
CLAUDE.md Loading commit data...
INDEX_FIELDS_DOCUMENTATION.md Loading commit data...
README.md Loading commit data...
USER_GUIDE.md Loading commit data...
customer1_data.sql Loading commit data...
environment.yml Loading commit data...
example_usage.py Loading commit data...
main.py Loading commit data...
requirements.txt Loading commit data...
requirements_server.txt Loading commit data...
restart.sh Loading commit data...
run.sh Loading commit data...
setup.sh Loading commit data...
test_data.sql Loading commit data...
test_data_tenant1.sql Loading commit data...
user_guide.md Loading commit data...
商品数据源入ES配置规范.md Loading commit data...
环境相关.md Loading commit data...
设计文档.md Loading commit data...
阿里opensearch电商行业.md Loading commit data...

README.md

电商搜索引擎 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

使用示例

1. 导入 Mock 数据到 MySQL(tenant_id=1)

# 生成并导入 100 个 Mock SPU(默认)
./scripts/mock_data.sh

# 生成并导入指定数量的 SPU
./scripts/mock_data.sh 1 200

# 使用显式参数
./scripts/mock_data.sh --mode mock --tenant-id 1 --num-spus 200

2. 导入 CSV 数据到 MySQL(tenant_id=2)

# 导入 customer1 的 CSV 数据
./scripts/mock_data.sh --mode csv \
  --csv-file data/customer1/goods_with_pic.5years_congku.csv.shuf.1w \
  --tenant-id 2 \
  --start-spu-id 1

3. 从 MySQL 导入数据到 Elasticsearch

# 导入 tenant_id=1 的数据
./scripts/ingest.sh 1

# 重建索引并导入数据
./scripts/ingest.sh 1 true

4. 启动服务

# 启动前端和后端
./run.sh

# 重启所有服务
./restart.sh

完整工作流程示例

# 1. 导入 Mock 数据(tenant_id=1)
./scripts/mock_data.sh 1 100

# 2. 导入 CSV 数据(tenant_id=2)
./scripts/mock_data.sh --mode csv \
  --csv-file data/customer1/goods_with_pic.5years_congku.csv.shuf.1w \
  --tenant-id 2

# 3. 将两个租户的数据导入 ES
./scripts/ingest.sh 1
./scripts/ingest.sh 2

# 4. 启动服务
./run.sh

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

许可证

专有软件 - 保留所有权利