# 电商搜索引擎 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_spu` 和 `shoplazza_product_sku` 表) ### 技术栈 - **后端框架**:Python 3.8+ / FastAPI - **搜索引擎**:Elasticsearch 8.x - **数据库**:MySQL(店匠数据表) - **向量模型**: - 文本向量:BGE-M3(1024维) - 图片向量:CN-CLIP(1024维) - **翻译服务**:DeepL API - **前端**:HTML + JavaScript ## 快速开始 ### 1. 环境准备 #### 安装依赖 ```bash pip install -r requirements.txt ``` #### 启动 Elasticsearch ```bash # 使用 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` 文件: ```bash 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)** ```bash # 生成并导入 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)** ```bash # 导入 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** ```bash # 导入 tenant_id=1 的数据 ./scripts/ingest.sh 1 # 重建索引并导入数据 ./scripts/ingest.sh 1 true ``` **4. 启动服务** ```bash # 启动前端和后端 ./run.sh # 重启所有服务 ./restart.sh ``` #### 完整工作流程示例 ```bash # 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 服务(可选) 如果不想使用脚本,可以手动启动: ```bash python -m api.app \ --host 0.0.0.0 \ --port 6002 \ --es-host http://localhost:9200 \ --reload ``` ### 4. 测试搜索 #### 简单搜索 ```bash curl -X POST http://localhost:6002/search/ \ -H "Content-Type: application/json" \ -d '{ "query": "蓝牙耳机", "size": 10 }' ``` #### 带过滤器的搜索 ```bash 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 } } }' ``` #### 布尔表达式搜索 ```bash curl -X POST http://localhost:6002/search/ \ -H "Content-Type: application/json" \ -d '{ "query": "蓝牙 AND (耳机 OR 音响)", "size": 10 }' ``` #### 图片搜索 ```bash curl -X POST http://localhost:6002/search/image \ -H "Content-Type: application/json" \ -d '{ "image_url": "https://oss.essa.cn/example.jpg", "size": 10 }' ``` #### 分面搜索 ```bash 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 索引的字段结构: ```yaml 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) 定义多域查询配置: ```yaml 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) 多语言和翻译配置: ```yaml query_config: languages: ["zh", "en", "ru"] auto_translate: true translation_api: "deepl" enable_embeddings: true embedding_model: "bge-m3" ``` #### 4. 排序配置 (ranking) 相关性排序表达式: ```yaml ranking: expression: "bm25() + 0.2*text_embedding_relevance() + general_score*2" enable_function_score: true ``` #### 5. SPU 配置 (spu_config) SPU 聚合配置: ```yaml 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/` **请求示例**: ```json { "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` **请求示例**: ```json { "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 ``` ### 查询改写 配置品牌/分类映射: ```yaml 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: ```yaml 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.py` 或 `scripts/import_tenant2_csv.py` 生成 SQL 文件 2. **导入 MySQL**:使用 `scripts/mock_data.sh` 或 `scripts/import_test_data.py` 导入数据 3. **索引到 ES**:使用 `scripts/ingest.sh` 或 `scripts/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. 向量缓存 启用向量缓存可以避免重复计算: ```python # 在配置中启用缓存 cache: enabled: true ttl: 3600 # 缓存过期时间(秒) ``` ### 2. 批量处理 调整批量大小以优化性能: - **数据转换批量大小**:默认 100(根据内存调整) - **索引批量大小**:默认 500(根据 ES 性能调整) ### 3. GPU 加速 使用 GPU 加速向量计算: ```bash # 安装 CUDA 版本的 PyTorch pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 ``` ### 4. ES 分片配置 根据数据量配置 ES 分片: ```yaml # 在 mapping 生成器中配置 settings: number_of_shards: 3 number_of_replicas: 1 ``` ## 开发指南 ### 运行测试 ```bash pytest tests/ ``` ### 代码格式化 ```bash black . ``` ### 类型检查 ```bash mypy . ``` ### 日志查看 ```bash # 查看服务日志 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` ## 许可证 专有软件 - 保留所有权利