diff --git a/.cursor/plans/所有租户共用一套统一配置.tenantID只在请求层级.服务层级没有tenantID相关的独立配置.md b/.cursor/plans/所有租户共用一套统一配置.tenantID只在请求层级.服务层级没有tenantID相关的独立配置.md new file mode 100644 index 0000000..e427dfb --- /dev/null +++ b/.cursor/plans/所有租户共用一套统一配置.tenantID只在请求层级.服务层级没有tenantID相关的独立配置.md @@ -0,0 +1,342 @@ + +# 多租户架构重构计划 + +## 概述 + +将搜索服务从按租户启动改造为真正的多租户架构: + +- 服务启动时不指定租户ID,所有租户共用一套配置 +- 删除customer1配置,去掉base层级,统一为config.yaml +- 统一脚本接口:启动、停止、重启、数据灌入 +- 统一数据灌入流程,ES只有一份索引 +- 前端支持在搜索框左侧输入租户ID + +## Phase 1: 配置文件体系重构 + +### 1.1 创建统一配置文件 + +**文件**: `config/config.yaml` (NEW) + +- 将 `config/schema/base/config.yaml` 移动到 `config/config.yaml` +- 删除 `customer_name` 字段(不再需要) +- 删除 `customer_id` 相关逻辑 +- 固定索引名称为 `search_products` +- 确保包含 `tenant_id` 字段(必需) + +### 1.2 删除customer1配置 + +**删除文件**: + +- `config/schema/customer1/config.yaml` +- `config/schema/customer1/` 目录(如果为空) + +### 1.3 更新ConfigLoader + +**文件**: `config/config_loader.py` + +修改 `load_customer_config()` 方法: + +- 移除 `customer_id` 参数 +- 改为 `load_config()` 方法 +- 直接加载 `config/config.yaml` +- 移除对 `config/schema/{customer_id}/config.yaml` 的查找逻辑 +- 移除 `customer_id` 字段验证 +- 更新 `CustomerConfig` 类:移除 `customer_id` 字段 + +### 1.4 更新配置验证 + +**文件**: `config/config_loader.py` + +修改 `validate_config()` 方法: + +- 确保 `tenant_id` 字段存在且为必需 +- 移除对 `customer_id` 的验证 + +## Phase 2: 服务启动改造 + +### 2.1 更新API应用初始化 + +**文件**: `api/app.py` + +修改 `init_service()` 方法: + +- 移除 `customer_id` 参数 +- 直接加载统一配置(`config/config.yaml`) +- 移除 `CUSTOMER_ID` 环境变量依赖 +- 更新日志输出(不再显示customer_id) + +修改 `startup_event()` 方法: + +- 移除 `CUSTOMER_ID` 环境变量读取 +- 直接调用 `init_service()` 不传参数 + +### 2.2 更新main.py + +**文件**: `main.py` + +修改 `cmd_serve()` 方法: + +- 移除 `--customer` 参数 +- 移除 `CUSTOMER_ID` 环境变量设置 +- 更新帮助信息 + +### 2.3 更新启动脚本 + +**文件**: `scripts/start_backend.sh` + +修改: + +- 移除 `CUSTOMER_ID` 环境变量 +- 移除 `--customer` 参数 +- 简化启动命令 + +**文件**: `scripts/start_servers.py` + +修改 `start_api_server()` 方法: + +- 移除 `customer` 参数 +- 移除 `CUSTOMER_ID` 环境变量设置 +- 简化启动命令 + +## Phase 3: 脚本体系统一 + +### 3.1 创建统一启动脚本 + +**文件**: `scripts/start.sh` (NEW) + +功能: + +- 启动后端服务(调用 `scripts/start_backend.sh`) +- 启动前端服务(调用 `scripts/start_frontend.sh`) +- 等待服务就绪 +- 显示服务状态和访问地址 + +### 3.2 创建统一停止脚本 + +**文件**: `scripts/stop.sh` (已存在,需更新) + +功能: + +- 停止后端服务(端口6002) +- 停止前端服务(端口6003) +- 清理PID文件 +- 显示停止状态 + +### 3.3 创建统一重启脚本 + +**文件**: `scripts/restart.sh` (已存在,需更新) + +功能: + +- 调用 `scripts/stop.sh` 停止服务 +- 等待服务完全停止 +- 调用 `scripts/start.sh` 启动服务 + +### 3.4 创建数据灌入脚本 + +**文件**: `scripts/ingest.sh` (已存在,需更新) + +功能: + +- 从MySQL读取数据 +- 转换数据格式(统一处理base和customer1数据源) +- 灌入到ES索引 `search_products` +- 支持指定租户ID过滤数据 +- 自动处理字段映射:缺失字段随机生成,多余字段忽略 + +### 3.5 创建Mock数据脚本 + +**文件**: `scripts/mock_data.sh` (NEW) + +功能: + +- 生成测试数据到MySQL +- 支持指定租户ID +- 支持指定数据量 +- 调用 `scripts/generate_test_data.py` 和 `scripts/import_test_data.py` + +### 3.6 更新根目录脚本 + +**文件**: `run.sh` (已存在,需更新) + +功能: + +- 调用 `scripts/start.sh` 启动服务 + +**文件**: `restart.sh` (已存在,需更新) + +功能: + +- 调用 `scripts/restart.sh` 重启服务 + +**文件**: `setup.sh` (已存在,需更新) + +功能: + +- 设置环境 +- 检查依赖 +- 不包含服务启动逻辑 + +**文件**: `test_all.sh` (已存在,需更新) + +功能: + +- 运行完整测试流程 +- 包含数据灌入、服务启动、API测试 + +### 3.7 清理废弃脚本 + +**删除文件**: + +- `scripts/demo_base.sh` +- `scripts/stop_base.sh` +- `scripts/start_test_environment.sh` +- `scripts/stop_test_environment.sh` +- 其他不再需要的脚本 + +## Phase 4: 数据灌入统一 + +### 4.1 更新数据灌入脚本 + +**文件**: `scripts/ingest_shoplazza.py` + +修改: + +- 移除 `--config` 参数(不再需要) +- 直接加载统一配置(`config/config.yaml`) +- 统一处理所有数据源(不再区分base和customer1) +- 支持 `--tenant-id` 参数过滤数据 +- 字段映射逻辑: +- 如果字段在配置中但数据源中没有,随机生成 +- 如果字段在数据源中但配置中没有,忽略 +- 确保 `tenant_id` 字段正确设置 + +### 4.2 更新数据转换器 + +**文件**: `indexer/spu_transformer.py` + +修改: + +- 移除对配置中 `customer_id` 的依赖 +- 统一处理所有数据源 +- 确保字段映射正确(缺失字段随机生成,多余字段忽略) + +### 4.3 统一测试数据生成 + +**文件**: `scripts/generate_test_data.py` + +修改: + +- 支持生成符合统一索引结构的测试数据 +- 支持指定租户ID +- 确保生成的数据包含所有必需字段 + +## Phase 5: 前端改造 + +### 5.1 更新前端HTML + +**文件**: `frontend/index.html` + +修改: + +- 在搜索框左侧添加租户ID输入框 +- 添加租户ID标签 +- 更新布局样式 + +### 5.2 更新前端JavaScript + +**文件**: `frontend/static/js/app_base.js` + +修改: + +- 移除硬编码的 `TENANT_ID = '1'` +- 从输入框读取租户ID +- 在搜索请求中发送租户ID(通过 `X-Tenant-ID` header) +- 添加租户ID验证(不能为空) +- 更新UI显示 + +### 5.3 更新前端CSS + +**文件**: `frontend/static/css/style.css` + +修改: + +- 添加租户ID输入框样式 +- 更新搜索栏布局(支持租户ID输入框) + +## Phase 6: 更新文档和测试 + +### 6.1 更新README + +**文件**: `README.md` + +修改: + +- 更新启动说明(不再需要指定租户ID) +- 更新配置说明(统一配置文件) +- 更新脚本使用说明 + +### 6.2 更新API文档 + +**文件**: `API_DOCUMENTATION.md` + +修改: + +- 更新租户ID说明(必须通过请求提供) +- 更新配置说明(统一配置) + +### 6.3 更新测试脚本 + +**文件**: `test_all.sh` + +修改: + +- 更新测试流程(不再需要指定租户ID) +- 更新数据灌入测试(统一数据源) +- 更新API测试(包含租户ID参数) + +## Phase 7: 清理和验证 + +### 7.1 清理废弃代码 + +- 删除所有对 `customer_id` 的引用 +- 删除所有对 `customer1` 配置的引用 +- 删除所有对 `base` 配置层级的引用 +- 清理不再使用的脚本 + +### 7.2 验证功能 + +- 验证服务启动(不指定租户ID) +- 验证配置加载(统一配置) +- 验证数据灌入(统一数据源) +- 验证搜索功能(通过请求提供租户ID) +- 验证前端功能(租户ID输入) + +## 关键文件清单 + +### 需要修改的文件: + +1. `config/config_loader.py` - 移除customer_id逻辑 +2. `config/config.yaml` - 统一配置文件(从base移动) +3. `api/app.py` - 移除customer_id参数 +4. `main.py` - 移除customer参数 +5. `scripts/start_backend.sh` - 移除CUSTOMER_ID +6. `scripts/start_servers.py` - 移除customer参数 +7. `scripts/ingest_shoplazza.py` - 统一数据灌入 +8. `frontend/index.html` - 添加租户ID输入框 +9. `frontend/static/js/app_base.js` - 读取租户ID +10. `run.sh`, `restart.sh`, `setup.sh`, `test_all.sh` - 更新脚本 + +### 需要删除的文件: + +1. `config/schema/customer1/config.yaml` +2. `config/schema/customer1/` 目录 +3. `scripts/demo_base.sh` +4. `scripts/stop_base.sh` +5. 其他废弃脚本 + +### 需要创建的文件: + +1. `config/config.yaml` - 统一配置文件 +2. `scripts/start.sh` - 统一启动脚本 +3. `scripts/mock_data.sh` - Mock数据脚本 \ No newline at end of file diff --git a/api/app.py b/api/app.py index fda892d..16eab02 100644 --- a/api/app.py +++ b/api/app.py @@ -51,28 +51,27 @@ _searcher: Optional[Searcher] = None _query_parser: Optional[QueryParser] = None -def init_service(customer_id: str = "customer1", es_host: str = "http://localhost:9200"): +def init_service(es_host: str = "http://localhost:9200"): """ - Initialize search service with configuration. + Initialize search service with unified configuration. Args: - customer_id: Customer configuration ID es_host: Elasticsearch host URL """ global _config, _es_client, _searcher, _query_parser - print(f"Initializing search service for customer: {customer_id}") + print("Initializing search service (multi-tenant)") - # Load configuration - config_loader = ConfigLoader("config/schema") - _config = config_loader.load_customer_config(customer_id) + # Load unified configuration + config_loader = ConfigLoader("config/config.yaml") + _config = config_loader.load_config() # Validate configuration errors = config_loader.validate_config(_config) if errors: raise ValueError(f"Configuration validation failed: {errors}") - print(f"Configuration loaded: {_config.customer_name}") + print(f"Configuration loaded: {_config.es_index_name}") # Get ES credentials from environment variables or .env file es_username = os.getenv('ES_USERNAME') @@ -113,7 +112,7 @@ def init_service(customer_id: str = "customer1", es_host: str = "http://localhos def get_config() -> CustomerConfig: - """Get customer configuration.""" + """Get search engine configuration.""" if _config is None: raise RuntimeError("Service not initialized") return _config @@ -184,15 +183,13 @@ app.add_middleware( @app.on_event("startup") async def startup_event(): """Initialize service on startup.""" - customer_id = os.getenv("CUSTOMER_ID", "customer1") es_host = os.getenv("ES_HOST", "http://localhost:9200") - logger.info(f"Starting E-Commerce Search API") - logger.info(f"Customer ID: {customer_id}") + logger.info("Starting E-Commerce Search API (Multi-Tenant)") logger.info(f"Elasticsearch Host: {es_host}") try: - init_service(customer_id=customer_id, es_host=es_host) + init_service(es_host=es_host) logger.info("Service initialized successfully") except Exception as e: logger.error(f"Failed to initialize service: {e}") @@ -310,16 +307,14 @@ else: if __name__ == "__main__": import uvicorn - parser = argparse.ArgumentParser(description='Start search API service') + parser = argparse.ArgumentParser(description='Start search API service (multi-tenant)') parser.add_argument('--host', default='0.0.0.0', help='Host to bind to') parser.add_argument('--port', type=int, default=6002, help='Port to bind to') - parser.add_argument('--customer', default='customer1', help='Customer ID') parser.add_argument('--es-host', default='http://localhost:9200', help='Elasticsearch host') parser.add_argument('--reload', action='store_true', help='Enable auto-reload') args = parser.parse_args() - # Set environment variables - os.environ['CUSTOMER_ID'] = args.customer + # Set environment variable os.environ['ES_HOST'] = args.es_host # Run server diff --git a/api/models.py b/api/models.py index 9998da7..59de609 100644 --- a/api/models.py +++ b/api/models.py @@ -250,7 +250,6 @@ class HealthResponse(BaseModel): """Health check response model.""" status: str = Field(..., description="Service status") elasticsearch: str = Field(..., description="Elasticsearch status") - customer_id: str = Field(..., description="Customer configuration ID") class ErrorResponse(BaseModel): diff --git a/api/routes/admin.py b/api/routes/admin.py index 8802156..1b889ac 100644 --- a/api/routes/admin.py +++ b/api/routes/admin.py @@ -28,15 +28,13 @@ async def health_check(): return HealthResponse( status="healthy" if es_status == "connected" else "unhealthy", - elasticsearch=es_status, - customer_id=config.customer_id + elasticsearch=es_status ) except Exception as e: return HealthResponse( status="unhealthy", - elasticsearch="error", - customer_id="unknown" + elasticsearch="error" ) @@ -51,8 +49,6 @@ async def get_configuration(): config = get_config() return { - "customer_id": config.customer_id, - "customer_name": config.customer_name, "es_index_name": config.es_index_name, "num_fields": len(config.fields), "num_indexes": len(config.indexes), diff --git a/config/config.yaml b/config/config.yaml new file mode 100644 index 0000000..509a5e5 --- /dev/null +++ b/config/config.yaml @@ -0,0 +1,269 @@ +# Unified Configuration for Multi-Tenant Search Engine +# 统一配置文件,所有租户共用一套索引配置 +# 注意:此配置不包含MySQL相关配置,只包含ES搜索相关配置 + +# Elasticsearch Index +es_index_name: "search_products" + +# ES Index Settings +es_settings: + number_of_shards: 1 + number_of_replicas: 0 + refresh_interval: "30s" + +# Field Definitions (SPU级别,只包含对搜索有帮助的字段) +fields: + # 租户隔离字段(必需) + - name: "tenant_id" + type: "KEYWORD" + required: true + index: true + store: true + + # 商品标识字段 + - name: "product_id" + type: "KEYWORD" + required: true + index: true + store: true + + - name: "handle" + type: "KEYWORD" + index: true + store: true + + # 文本搜索字段 + - name: "title" + type: "TEXT" + analyzer: "chinese_ecommerce" + boost: 3.0 + index: true + store: true + + - name: "brief" + type: "TEXT" + analyzer: "chinese_ecommerce" + boost: 1.5 + index: true + store: true + + - name: "description" + type: "TEXT" + analyzer: "chinese_ecommerce" + boost: 1.0 + index: true + store: true + + # SEO字段(提升相关性) + - name: "seo_title" + type: "TEXT" + analyzer: "chinese_ecommerce" + boost: 2.0 + index: true + store: true + + - name: "seo_description" + type: "TEXT" + analyzer: "chinese_ecommerce" + boost: 1.5 + index: true + store: true + + - name: "seo_keywords" + type: "TEXT" + analyzer: "chinese_ecommerce" + boost: 2.0 + index: true + store: true + + # 分类和标签字段(TEXT + KEYWORD双重索引) + - name: "vendor" + type: "TEXT" + analyzer: "chinese_ecommerce" + boost: 1.5 + index: true + store: true + + - name: "vendor_keyword" + type: "KEYWORD" + index: true + store: false + + - name: "product_type" + type: "TEXT" + analyzer: "chinese_ecommerce" + boost: 1.5 + index: true + store: true + + - name: "product_type_keyword" + type: "KEYWORD" + index: true + store: false + + - name: "tags" + type: "TEXT" + analyzer: "chinese_ecommerce" + boost: 1.0 + index: true + store: true + + - name: "tags_keyword" + type: "KEYWORD" + index: true + store: false + + - name: "category" + type: "TEXT" + analyzer: "chinese_ecommerce" + boost: 1.5 + index: true + store: true + + - name: "category_keyword" + type: "KEYWORD" + index: true + store: false + + # 价格字段(扁平化) + - name: "min_price" + type: "FLOAT" + index: true + store: true + + - name: "max_price" + type: "FLOAT" + index: true + store: true + + - name: "compare_at_price" + type: "FLOAT" + index: true + store: true + + # 图片字段(用于显示,不参与搜索) + - name: "image_url" + type: "KEYWORD" + index: false + store: true + + # 嵌套variants字段 + - name: "variants" + type: "JSON" + nested: true + nested_properties: + variant_id: + type: "keyword" + index: true + store: true + title: + type: "text" + analyzer: "chinese_ecommerce" + index: true + store: true + price: + type: "float" + index: true + store: true + compare_at_price: + type: "float" + index: true + store: true + sku: + type: "keyword" + index: true + store: true + stock: + type: "long" + index: true + store: true + options: + type: "object" + enabled: true + +# Index Structure (Query Domains) +indexes: + - name: "default" + label: "默认索引" + fields: + - "title" + - "brief" + - "description" + - "seo_title" + - "seo_description" + - "seo_keywords" + - "vendor" + - "product_type" + - "tags" + - "category" + analyzer: "chinese_ecommerce" + boost: 1.0 + + - name: "title" + label: "标题索引" + fields: + - "title" + - "seo_title" + analyzer: "chinese_ecommerce" + boost: 2.0 + + - name: "vendor" + label: "品牌索引" + fields: + - "vendor" + analyzer: "chinese_ecommerce" + boost: 1.5 + + - name: "category" + label: "类目索引" + fields: + - "category" + analyzer: "chinese_ecommerce" + boost: 1.5 + + - name: "tags" + label: "标签索引" + fields: + - "tags" + - "seo_keywords" + analyzer: "chinese_ecommerce" + boost: 1.0 + +# Query Configuration +query_config: + supported_languages: + - "zh" + - "en" + default_language: "zh" + enable_translation: true + enable_text_embedding: true + enable_query_rewrite: true + + # Translation API (DeepL) + translation_service: "deepl" + translation_api_key: null # Set via environment variable + +# Ranking Configuration +ranking: + expression: "bm25() + 0.2*text_embedding_relevance()" + description: "BM25 text relevance combined with semantic embedding similarity" + +# Function Score配置(ES层打分规则) +function_score: + score_mode: "sum" + boost_mode: "multiply" + + functions: [] + +# Rerank配置(本地重排,当前禁用) +rerank: + enabled: false + expression: "" + description: "Local reranking (disabled, use ES function_score instead)" + +# SPU配置(已启用,使用嵌套variants) +spu_config: + enabled: true + spu_field: "product_id" + inner_hits_size: 10 + diff --git a/config/config_loader.py b/config/config_loader.py index 9b2eb92..40c19b0 100644 --- a/config/config_loader.py +++ b/config/config_loader.py @@ -86,10 +86,7 @@ class RerankConfig: @dataclass class CustomerConfig: - """Complete configuration for a customer.""" - customer_id: str - customer_name: str - + """Complete configuration for search engine (multi-tenant).""" # Field definitions fields: List[FieldConfig] @@ -122,22 +119,20 @@ class ConfigurationError(Exception): class ConfigLoader: - """Loads and validates customer configurations from YAML files.""" + """Loads and validates unified search engine configuration from YAML file.""" - def __init__(self, config_dir: str = "config/schema"): - self.config_dir = Path(config_dir) + def __init__(self, config_file: str = "config/config.yaml"): + self.config_file = Path(config_file) - def _load_rewrite_dictionary(self, customer_id: str) -> Dict[str, str]: + def _load_rewrite_dictionary(self) -> Dict[str, str]: """ Load query rewrite dictionary from external file. - Args: - customer_id: Customer identifier - Returns: Dictionary mapping query terms to rewritten queries """ - dict_file = self.config_dir / customer_id / "query_rewrite.dict" + # Try config/query_rewrite.dict first + dict_file = self.config_file.parent / "query_rewrite.dict" if not dict_file.exists(): # Dictionary file is optional, return empty dict if not found @@ -166,16 +161,9 @@ class ConfigLoader: return rewrite_dict - def load_customer_config(self, customer_id: str) -> CustomerConfig: + def load_config(self) -> CustomerConfig: """ - Load customer configuration from YAML file. - - Supports two directory structures: - 1. New structure: config/schema/{customer_id}/config.yaml - 2. Old structure: config/schema/{customer_id}_config.yaml (for backward compatibility) - - Args: - customer_id: Customer identifier (used to find config file) + Load unified configuration from YAML file. Returns: CustomerConfig object @@ -183,25 +171,18 @@ class ConfigLoader: Raises: ConfigurationError: If config file not found or invalid """ - # Try new directory structure first - config_file = self.config_dir / customer_id / "config.yaml" - - # Fall back to old structure if new one doesn't exist - if not config_file.exists(): - config_file = self.config_dir / f"{customer_id}_config.yaml" - - if not config_file.exists(): - raise ConfigurationError(f"Configuration file not found: {config_file}") + if not self.config_file.exists(): + raise ConfigurationError(f"Configuration file not found: {self.config_file}") try: - with open(config_file, 'r', encoding='utf-8') as f: + with open(self.config_file, 'r', encoding='utf-8') as f: config_data = yaml.safe_load(f) except yaml.YAMLError as e: - raise ConfigurationError(f"Invalid YAML in {config_file}: {e}") + raise ConfigurationError(f"Invalid YAML in {self.config_file}: {e}") - return self._parse_config(config_data, customer_id) + return self._parse_config(config_data) - def _parse_config(self, config_data: Dict[str, Any], customer_id: str) -> CustomerConfig: + def _parse_config(self, config_data: Dict[str, Any]) -> CustomerConfig: """Parse configuration dictionary into CustomerConfig object.""" # Parse fields @@ -218,7 +199,7 @@ class ConfigLoader: query_config_data = config_data.get("query_config", {}) # Load rewrite dictionary from external file instead of config - rewrite_dictionary = self._load_rewrite_dictionary(customer_id) + rewrite_dictionary = self._load_rewrite_dictionary() query_config = QueryConfig( supported_languages=query_config_data.get("supported_languages", ["zh", "en"]), @@ -263,8 +244,6 @@ class ConfigLoader: ) return CustomerConfig( - customer_id=customer_id, - customer_name=config_data.get("customer_name", customer_id), fields=fields, indexes=indexes, query_config=query_config, @@ -272,7 +251,7 @@ class ConfigLoader: function_score=function_score, rerank=rerank, spu_config=spu_config, - es_index_name=config_data.get("es_index_name", f"search_{customer_id}"), + es_index_name=config_data.get("es_index_name", "search_products"), es_settings=config_data.get("es_settings", {}) ) @@ -430,23 +409,21 @@ class ConfigLoader: def save_config(self, config: CustomerConfig, output_path: Optional[str] = None) -> None: """ - Save customer configuration to YAML file. + Save configuration to YAML file. Note: rewrite_dictionary is saved separately to query_rewrite.dict file Args: config: Configuration to save - output_path: Optional output path (defaults to new directory structure) + output_path: Optional output path (defaults to config/config.yaml) """ if output_path is None: - # Use new directory structure by default - customer_dir = self.config_dir / config.customer_id - customer_dir.mkdir(parents=True, exist_ok=True) - output_path = customer_dir / "config.yaml" + output_path = self.config_file + else: + output_path = Path(output_path) # Convert config back to dictionary format config_dict = { - "customer_name": config.customer_name, "es_index_name": config.es_index_name, "es_settings": config.es_settings, "fields": [self._field_to_dict(field) for field in config.fields], @@ -482,23 +459,22 @@ class ConfigLoader: } } + output_path.parent.mkdir(parents=True, exist_ok=True) with open(output_path, 'w', encoding='utf-8') as f: yaml.dump(config_dict, f, default_flow_style=False, allow_unicode=True) # Save rewrite dictionary to separate file - self._save_rewrite_dictionary(config.customer_id, config.query_config.rewrite_dictionary) + self._save_rewrite_dictionary(config.query_config.rewrite_dictionary) - def _save_rewrite_dictionary(self, customer_id: str, rewrite_dict: Dict[str, str]) -> None: + def _save_rewrite_dictionary(self, rewrite_dict: Dict[str, str]) -> None: """ Save rewrite dictionary to external file. Args: - customer_id: Customer identifier rewrite_dict: Dictionary to save """ - customer_dir = self.config_dir / customer_id - customer_dir.mkdir(parents=True, exist_ok=True) - dict_file = customer_dir / "query_rewrite.dict" + dict_file = self.config_file.parent / "query_rewrite.dict" + dict_file.parent.mkdir(parents=True, exist_ok=True) with open(dict_file, 'w', encoding='utf-8') as f: for key, value in rewrite_dict.items(): diff --git a/config/query_rewrite.dict b/config/query_rewrite.dict new file mode 100644 index 0000000..8e5ce37 --- /dev/null +++ b/config/query_rewrite.dict @@ -0,0 +1,4 @@ +芭比 brand:芭比 OR name:芭比娃娃 +玩具 category:玩具 +消防 category:消防 OR name:消防 + diff --git a/frontend/index.html b/frontend/index.html index ba1afbc..53fe9f8 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -21,6 +21,10 @@