From 362d43b67e9a728680610a07147a27db33adf256 Mon Sep 17 00:00:00 2001 From: tangwang Date: Thu, 13 Nov 2025 14:43:48 +0800 Subject: [PATCH] 店匠体系数据的搜索 --- .env | 3 +++ config/env_config.py | 4 ++++ config/schema/base/config.yaml | 271 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- config/schema/customer1/config.yaml | 363 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- config/schema/customer1/query_rewrite.dict | 4 ---- frontend/index.html | 4 ++-- frontend/static/js/app.js | 6 ++++-- scripts/create_base_frontend.py | 218 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- scripts/demo_base.sh | 198 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ scripts/frontend_server.py | 51 +++++++++++++++++++++++++++++++++++++++++---------- scripts/stop_base.sh | 45 --------------------------------------------- 11 files changed, 54 insertions(+), 1113 deletions(-) delete mode 100644 config/schema/base/config.yaml delete mode 100644 config/schema/customer1/config.yaml delete mode 100644 config/schema/customer1/query_rewrite.dict delete mode 100755 scripts/create_base_frontend.py delete mode 100755 scripts/demo_base.sh delete mode 100755 scripts/stop_base.sh diff --git a/.env b/.env index f109c3f..78af395 100644 --- a/.env +++ b/.env @@ -31,3 +31,6 @@ IMAGE_MODEL_DIR=/data/tw/models/cn-clip # Cache Directory CACHE_DIR=.cache + +# Frontend API Base URL +API_BASE_URL=http://120.76.41.98:6002 diff --git a/config/env_config.py b/config/env_config.py index 52fdfc8..2e5bb45 100644 --- a/config/env_config.py +++ b/config/env_config.py @@ -37,6 +37,10 @@ CUSTOMER_ID = os.getenv('CUSTOMER_ID', 'customer1') # API Service Configuration API_HOST = os.getenv('API_HOST', '0.0.0.0') API_PORT = int(os.getenv('API_PORT', 6002)) +# API_BASE_URL: 如果未设置,根据API_HOST构建(0.0.0.0使用localhost) +API_BASE_URL = os.getenv('API_BASE_URL') +if not API_BASE_URL: + API_BASE_URL = f'http://localhost:{API_PORT}' if API_HOST == '0.0.0.0' else f'http://{API_HOST}:{API_PORT}' # Model Directories TEXT_MODEL_DIR = os.getenv('TEXT_MODEL_DIR', '/data/tw/models/bge-m3') diff --git a/config/schema/base/config.yaml b/config/schema/base/config.yaml deleted file mode 100644 index 5fc3013..0000000 --- a/config/schema/base/config.yaml +++ /dev/null @@ -1,271 +0,0 @@ -# Base Configuration for Shoplazza -# 店匠通用配置文件,所有使用店匠表的客户共用 -# 注意:此配置不包含MySQL相关配置,只包含ES搜索相关配置 - -customer_name: "Shoplazza Base Configuration" - -# 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/schema/customer1/config.yaml b/config/schema/customer1/config.yaml deleted file mode 100644 index 29054ee..0000000 --- a/config/schema/customer1/config.yaml +++ /dev/null @@ -1,363 +0,0 @@ -# Customer1 Configuration -# Test customer for cross-border e-commerce search - -customer_name: "Customer1 Test Instance" - -# MySQL Database Configuration -mysql_config: - host: "120.79.247.228" - port: 3316 - database: "saas" - username: "saas" - password: "P89cZHS5d7dFyc9R" - -# Table Configuration -main_table: "shoplazza_product_sku" -extension_table: "customer1_extension" - -# Elasticsearch Index -es_index_name: "search_customer1" - -# ES Index Settings -es_settings: - number_of_shards: 1 - number_of_replicas: 0 - refresh_interval: "30s" - -# Field Definitions -fields: - # Primary Key - - name: "skuId" - type: "LONG" - source_table: "main" - source_column: "id" - required: true - index: true - store: true - - # Text Fields - Multi-language - - name: "name" - type: "TEXT" - source_table: "extension" - source_column: "name" - analyzer: "chinese_ecommerce" - boost: 2.0 - index: true - store: true - - - name: "name_pinyin" - type: "TEXT" - source_table: "extension" - source_column: "name_pinyin" - analyzer: "standard" - boost: 1.5 - index: true - store: false - - - name: "ruSkuName" - type: "TEXT" - source_table: "extension" - source_column: "ruSkuName" - analyzer: "russian" - boost: 2.0 - index: true - store: true - - - name: "enSpuName" - type: "TEXT" - source_table: "extension" - source_column: "enSpuName" - analyzer: "english" - boost: 2.0 - index: true - store: true - - # Category and Brand - - name: "categoryName" - type: "TEXT" - source_table: "extension" - source_column: "categoryName" - analyzer: "chinese_ecommerce" - boost: 1.5 - index: true - store: true - - - name: "brandName" - type: "TEXT" - source_table: "extension" - source_column: "brandName" - analyzer: "chinese_ecommerce" - boost: 1.5 - index: true - store: true - - - name: "supplierName" - type: "TEXT" - source_table: "extension" - source_column: "supplierName" - analyzer: "chinese_ecommerce" - boost: 1.0 - index: true - store: true - - # Keyword Fields - - name: "categoryName_keyword" - type: "KEYWORD" - source_table: "extension" - source_column: "categoryName" - index: true - store: false - - - name: "brandName_keyword" - type: "KEYWORD" - source_table: "extension" - source_column: "brandName" - index: true - store: false - - - name: "supplierName_keyword" - type: "KEYWORD" - source_table: "extension" - source_column: "supplierName" - index: true - store: false - - # Price Fields - - name: "price" - type: "DOUBLE" - source_table: "extension" - source_column: "price" - index: true - store: true - - # Text Embedding Fields - - name: "name_embedding" - type: "TEXT_EMBEDDING" - source_table: "extension" - source_column: "name" - embedding_dims: 1024 - embedding_similarity: "dot_product" - index: true - - - name: "enSpuName_embedding" - type: "TEXT_EMBEDDING" - source_table: "extension" - source_column: "enSpuName" - embedding_dims: 1024 - embedding_similarity: "dot_product" - index: true - - # Image Fields - - name: "imageUrl" - type: "KEYWORD" - source_table: "extension" - source_column: "imageUrl" - index: true - store: true - - - name: "image_embedding" - type: "IMAGE_EMBEDDING" - source_table: "extension" - source_column: "imageUrl" - embedding_dims: 1024 - embedding_similarity: "dot_product" - nested: false - index: true - - # Metadata Fields - - name: "create_time" - type: "DATE" - source_table: "extension" - source_column: "create_time" - index: true - store: true - - - name: "days_since_last_update" - type: "INT" - source_table: "extension" - source_column: "days_since_last_update" - index: true - store: true - -# Index Structure (Query Domains) -indexes: - - name: "default" - label: "默认索引" - fields: - - "name" - - "enSpuName" - - "ruSkuName" - - "categoryName" - - "brandName" - analyzer: "chinese_ecommerce" - boost: 1.0 - example: 'query=default:"消防套"' - language_field_mapping: - zh: - - "name" - - "categoryName" - - "brandName" - en: - - "enSpuName" - ru: - - "ruSkuName" - - - name: "title" - label: "标题索引" - fields: - - "name" - - "enSpuName" - - "ruSkuName" - analyzer: "chinese_ecommerce" - boost: 2.0 - example: 'query=title:"芭比娃娃"' - language_field_mapping: - zh: - - "name" - en: - - "enSpuName" - ru: - - "ruSkuName" - - - name: "category" - label: "类目索引" - fields: - - "categoryName" - analyzer: "chinese_ecommerce" - boost: 1.5 - example: 'query=category:"玩具"' - - - name: "brand" - label: "品牌索引" - fields: - - "brandName" - analyzer: "chinese_ecommerce" - boost: 1.5 - example: 'query=brand:"ZHU LIN"' - -# Query Configuration -query_config: - supported_languages: - - "zh" - - "en" - - "ru" - 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是查询结构的必需部分 -function_score: - score_mode: "sum" # multiply, sum, avg, first, max, min - boost_mode: "multiply" # multiply, replace, sum, avg, max, min - - functions: - # 1. Filter + Weight(条件权重)- 根据条件匹配提权 - - type: "filter_weight" - name: "7天新品提权" - filter: - range: - days_since_last_update: - lte: 7 - weight: 1.3 - - - type: "filter_weight" - name: "30天新品提权" - filter: - range: - days_since_last_update: - lte: 30 - weight: 1.15 - - - type: "filter_weight" - name: "有视频提权" - filter: - term: - is_video: true - weight: 1.05 - - # 示例:特定标签提权 - # - type: "filter_weight" - # name: "特定标签提权" - # filter: - # term: - # labelId_by_skuId_essa_3: 165 - # weight: 1.1 - - # 示例:主力价格段提权 - # - type: "filter_weight" - # name: "主力价格段" - # filter: - # range: - # price: - # gte: 50 - # lte: 200 - # weight: 1.1 - - # 2. Field Value Factor(字段值映射)- 将数值字段映射为打分因子 - # 示例:在售天数 - # - type: "field_value_factor" - # name: "在售天数因子" - # field: "on_sell_days_boost" - # factor: 1.0 - # modifier: "none" # none, log, log1p, log2p, ln, ln1p, ln2p, square, sqrt, reciprocal - # missing: 1.0 - - # 示例:销量因子 - # - type: "field_value_factor" - # name: "销量因子" - # field: "sales_count" - # factor: 0.01 - # modifier: "log1p" # 对数映射,避免极端值影响 - # missing: 1.0 - - # 示例:评分因子 - # - type: "field_value_factor" - # name: "评分因子" - # field: "rating" - # factor: 0.5 - # modifier: "sqrt" - # missing: 1.0 - - # 3. Decay Functions(衰减函数)- 距离原点越远分数越低 - # 示例:时间衰减 - # - type: "decay" - # name: "时间衰减" - # function: "gauss" # gauss, exp, linear - # field: "create_time" - # origin: "now" - # scale: "30d" - # offset: "0d" - # decay: 0.5 - - # 示例:价格衰减 - # - type: "decay" - # name: "价格衰减" - # function: "linear" - # field: "price" - # origin: "100" - # scale: "50" - # decay: 0.5 - -# Rerank配置(本地重排,当前禁用) -rerank: - enabled: false - expression: "" - description: "Local reranking (disabled, use ES function_score instead)" - -# SPU Aggregation (disabled for customer1) -spu_config: - enabled: false - spu_field: null - inner_hits_size: 3 - diff --git a/config/schema/customer1/query_rewrite.dict b/config/schema/customer1/query_rewrite.dict deleted file mode 100644 index 8e5ce37..0000000 --- a/config/schema/customer1/query_rewrite.dict +++ /dev/null @@ -1,4 +0,0 @@ -芭比 brand:芭比 OR name:芭比娃娃 -玩具 category:玩具 -消防 category:消防 OR name:消防 - diff --git a/frontend/index.html b/frontend/index.html index 53fe9f8..5d78ebc 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -121,9 +121,9 @@ - + diff --git a/frontend/static/js/app.js b/frontend/static/js/app.js index 44fa819..63337c2 100644 --- a/frontend/static/js/app.js +++ b/frontend/static/js/app.js @@ -1,7 +1,9 @@ // SearchEngine Frontend - Modern UI (Multi-Tenant) -const API_BASE_URL = 'http://localhost:6002'; -document.getElementById('apiUrl').textContent = API_BASE_URL; +const API_BASE_URL = window.API_BASE_URL || 'http://localhost:6002'; +if (document.getElementById('apiUrl')) { + document.getElementById('apiUrl').textContent = API_BASE_URL; +} // Get tenant ID from input function getTenantId() { diff --git a/scripts/create_base_frontend.py b/scripts/create_base_frontend.py deleted file mode 100755 index e77a0aa..0000000 --- a/scripts/create_base_frontend.py +++ /dev/null @@ -1,218 +0,0 @@ -#!/usr/bin/env python3 -""" -Create frontend JavaScript file for base configuration. -""" - -import sys -import os -import argparse -import re -from pathlib import Path - -# Add parent directory to path -sys.path.insert(0, str(Path(__file__).parent.parent)) - - -def create_base_frontend_js(tenant_id: str, api_port: int = 6002, output_file: str = "frontend/static/js/app_base.js"): - """ - Create frontend JavaScript file for base configuration. - - Args: - tenant_id: Tenant ID - api_port: API port - output_file: Output file path - """ - # Read original app.js - original_file = Path(__file__).parent.parent / "frontend/static/js/app.js" - if not original_file.exists(): - print(f"ERROR: Original frontend file not found: {original_file}") - return 1 - - with open(original_file, 'r', encoding='utf-8') as f: - content = f.read() - - # Replace API_BASE_URL - api_url = f"http://localhost:{api_port}" - content = content.replace( - "const API_BASE_URL = 'http://120.76.41.98:6002';", - f"const API_BASE_URL = '{api_url}';" - ) - - # Add tenant_id constant at the beginning - content = content.replace( - "const API_BASE_URL =", - f"const TENANT_ID = '{tenant_id}';\nconst API_BASE_URL =" - ) - - # Update facets for base configuration - base_facets = ''' const facets = [ - { - "field": "category_keyword", - "size": 15, - "type": "terms" - }, - { - "field": "vendor_keyword", - "size": 15, - "type": "terms" - }, - { - "field": "tags_keyword", - "size": 10, - "type": "terms" - }, - { - "field": "min_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} - ] - } - ];''' - - # Find and replace facets definition (multiline match) - facets_pattern = r'const facets = \[.*?\];' - content = re.sub(facets_pattern, base_facets, content, flags=re.DOTALL) - - # Update fetch to include tenant_id header - content = content.replace( - "headers: {\n 'Content-Type': 'application/json',\n },", - f"headers: {{\n 'Content-Type': 'application/json',\n 'X-Tenant-ID': TENANT_ID,\n }}," - ) - - # Replace hits with results throughout - content = re.sub(r'\bdata\.hits\b', 'data.results', content) - content = re.sub(r'!data\.hits', '!data.results', content) - - # Replace hit loop with product loop - content = re.sub( - r'data\.hits\.forEach\(\(hit\) => \{', - 'data.results.forEach((product) => {', - content - ) - - # Remove source extraction lines - content = re.sub(r'const source = hit\._source;\s*\n', '', content) - content = re.sub(r'const score = hit\._custom_score \|\| hit\._score;\s*\n', 'const score = product.relevance_score;\n', content) - - # Replace all source. references with product. - content = re.sub(r'\bsource\.', 'product.', content) - - # Replace specific field names for base configuration - # imageUrl -> image_url - content = re.sub(r'product\.imageUrl', 'product.image_url', content) - # name -> title - content = re.sub(r'product\.name', 'product.title', content) - content = re.sub(r'product\.enSpuName', 'product.title', content) - # categoryName -> category - content = re.sub(r'product\.categoryName', 'product.category', content) - # brandName -> vendor - content = re.sub(r'product\.brandName', 'product.vendor', content) - # price -> price (already correct) - # Remove moq and quantity fields (not in base config) - content = re.sub(r'
.*?
\s*\n', '', content, flags=re.DOTALL) - content = re.sub(r'
.*?
\s*\n', '', content, flags=re.DOTALL) - - # Add stock and variants display - # Find the product-price div and add stock info after it - stock_info = '''
- ${product.in_stock ? 'In Stock' : 'Out of Stock'} - ${product.variants && product.variants.length > 0 ? `(${product.variants.length} variants)` : ''} -
- -''' - content = re.sub( - r'(
.*?
\s*\n)', - r'\1' + stock_info, - content, - flags=re.DOTALL - ) - - # Update price display format - content = re.sub( - r'\$\{product\.price \? `\$\{product\.price\} ₽` : \'N/A\'\}', - '${product.price ? `$${product.price.toFixed(2)}` : \'N/A\'}', - content - ) - - # Add compare_at_price if exists - content = re.sub( - r'(\$\{product\.price \? `\$\$\{product\.price\.toFixed\(2\)\}` : \'N/A\'\})', - r'\1${product.compare_at_price && product.compare_at_price > product.price ? `$${product.compare_at_price.toFixed(2)}` : \'\'}', - content - ) - - # Update product-meta to use base config fields - content = re.sub( - r'
\s*\$\{product\.category \? escapeHtml\(product\.category\) : \'\'\}\s*\$\{product\.vendor \? \' \| \' \+ escapeHtml\(product\.vendor\) : \'\'\}\s*
', - '
${product.vendor ? escapeHtml(product.vendor) : \'\'}${product.product_type ? \' | \' + escapeHtml(product.product_type) : \'\'}${product.category ? \' | \' + escapeHtml(product.category) : \'\'}
', - content - ) - - # Remove create_time display (not in base config) - content = re.sub( - r'\$\{product\.create_time \? `.*?\s*` : \'\'\}', - '', - content, - flags=re.DOTALL - ) - - # Add tags display if exists - tags_display = ''' ${product.tags ? ` -
- Tags: ${escapeHtml(product.tags)} -
- ` : ''}''' - - # Add tags before closing product-card div - content = re.sub( - r'(\s*\s*`;\s*\n\s*\}\);)', - tags_display + r'\n \n `;\n });', - content, - count=1 - ) - - # Update displayFacets for base configuration field names - content = re.sub( - r"facet\.field === 'categoryName_keyword'", - "facet.field === 'category_keyword'", - content - ) - content = re.sub( - r"facet\.field === 'brandName_keyword'", - "facet.field === 'vendor_keyword'", - content - ) - content = re.sub( - r"facet\.field === 'supplierName_keyword'", - "facet.field === 'tags_keyword'", - content - ) - - # Write output file - output_path = Path(__file__).parent.parent / output_file - output_path.parent.mkdir(parents=True, exist_ok=True) - - with open(output_path, 'w', encoding='utf-8') as f: - f.write(content) - - print(f"Created base frontend JavaScript: {output_path}") - return 0 - - -def main(): - parser = argparse.ArgumentParser(description='Create frontend JavaScript for base configuration') - parser.add_argument('--tenant-id', default='1', help='Tenant ID') - parser.add_argument('--api-port', type=int, default=6002, help='API port') - parser.add_argument('--output', default='frontend/static/js/app_base.js', help='Output file') - - args = parser.parse_args() - - return create_base_frontend_js(args.tenant_id, args.api_port, args.output) - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/scripts/demo_base.sh b/scripts/demo_base.sh deleted file mode 100755 index 3cf1f2a..0000000 --- a/scripts/demo_base.sh +++ /dev/null @@ -1,198 +0,0 @@ -#!/bin/bash - -# Base配置演示流程脚本 -# 用于演示店匠通用客户的搜索效果 - -set -e - -cd "$(dirname "$0")/.." -source /home/tw/miniconda3/etc/profile.d/conda.sh -conda activate searchengine - -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -NC='\033[0m' - -echo -e "${GREEN}========================================${NC}" -echo -e "${GREEN}Base配置演示流程${NC}" -echo -e "${GREEN}========================================${NC}" - -# 加载.env配置文件 -if [ -f .env ]; then - set -a - source .env - set +a -fi - -# 配置参数(从环境变量或默认值) -TENANT_ID=${1:-"1"} -DB_HOST=${DB_HOST:-"120.79.247.228"} -DB_PORT=${DB_PORT:-"3316"} -DB_DATABASE=${DB_DATABASE:-"saas"} -DB_USERNAME=${DB_USERNAME:-"saas"} -DB_PASSWORD=${DB_PASSWORD:-"P89cZHS5d7dFyc9R"} -ES_HOST=${ES_HOST:-"http://localhost:9200"} -API_PORT=${API_PORT:-"6002"} -FRONTEND_PORT=${FRONTEND_PORT:-"6003"} - -echo -e "\n${YELLOW}配置参数:${NC}" -echo " Tenant ID: $TENANT_ID" -echo " MySQL: $DB_HOST:$DB_PORT/$DB_DATABASE" -echo " Elasticsearch: $ES_HOST" -echo " API Port: $API_PORT" -echo " Frontend Port: $FRONTEND_PORT" - -# Step 1: 生成测试数据 -echo -e "\n${YELLOW}Step 1/5: 生成测试数据${NC}" -if [ ! -f "test_data_base.sql" ]; then - echo "生成100条SPU测试数据..." - python scripts/generate_test_data.py \ - --num-spus 100 \ - --tenant-id "$TENANT_ID" \ - --start-spu-id 1 \ - --start-sku-id 1 \ - --output test_data_base.sql - echo -e "${GREEN}✓ 测试数据已生成: test_data_base.sql${NC}" -else - echo -e "${YELLOW}⚠ 测试数据文件已存在,跳过生成${NC}" -fi - -# Step 2: 导入测试数据到MySQL -echo -e "\n${YELLOW}Step 2/5: 导入测试数据到MySQL${NC}" -if [ -z "$DB_PASSWORD" ]; then - echo -e "${RED}ERROR: DB_PASSWORD未设置,请检查.env文件或环境变量${NC}" - exit 1 -fi - -python scripts/import_test_data.py \ - --db-host "$DB_HOST" \ - --db-port "$DB_PORT" \ - --db-database "$DB_DATABASE" \ - --db-username "$DB_USERNAME" \ - --db-password "$DB_PASSWORD" \ - --sql-file test_data_base.sql \ - --tenant-id "$TENANT_ID" - -echo -e "${GREEN}✓ 测试数据已导入MySQL${NC}" - -# Step 3: 导入数据到Elasticsearch -echo -e "\n${YELLOW}Step 3/5: 导入数据到Elasticsearch${NC}" -python scripts/ingest_shoplazza.py \ - --db-host "$DB_HOST" \ - --db-port "$DB_PORT" \ - --db-database "$DB_DATABASE" \ - --db-username "$DB_USERNAME" \ - --db-password "$DB_PASSWORD" \ - --tenant-id "$TENANT_ID" \ - --config base \ - --es-host "$ES_HOST" \ - --recreate \ - --batch-size 500 - -echo -e "${GREEN}✓ 数据已导入Elasticsearch${NC}" - -# Step 4: 启动后端服务 -echo -e "\n${YELLOW}Step 4/5: 启动后端服务${NC}" -echo "后端服务将在后台运行..." - -# 创建logs目录 -mkdir -p logs - -# 启动后端(后台运行) -nohup python api/app.py \ - --host 0.0.0.0 \ - --port "$API_PORT" \ - --customer base \ - --es-host "$ES_HOST" > logs/backend_base.log 2>&1 & - -BACKEND_PID=$! -echo $BACKEND_PID > logs/backend_base.pid -echo -e "${GREEN}后端服务已启动 (PID: $BACKEND_PID)${NC}" -echo -e "${GREEN}日志文件: logs/backend_base.log${NC}" - -# 等待后端启动 -echo -e "${YELLOW}等待后端服务启动...${NC}" -MAX_RETRIES=15 -RETRY_COUNT=0 -BACKEND_READY=false - -while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do - sleep 2 - if curl -s "http://localhost:$API_PORT/health" > /dev/null 2>&1; then - BACKEND_READY=true - break - fi - RETRY_COUNT=$((RETRY_COUNT + 1)) - echo -e "${YELLOW} 等待中... ($RETRY_COUNT/$MAX_RETRIES)${NC}" -done - -if [ "$BACKEND_READY" = true ]; then - echo -e "${GREEN}✓ 后端服务运行正常${NC}" -else - echo -e "${YELLOW}⚠ 后端服务可能还在启动中,请稍后访问${NC}" -fi - -# Step 5: 启动前端服务 -echo -e "\n${YELLOW}Step 5/5: 启动前端服务${NC}" -echo "前端服务将在后台运行..." - -# 创建base配置的前端JS文件 -echo "创建base配置前端文件..." -python scripts/create_base_frontend.py --tenant-id "$TENANT_ID" --api-port "$API_PORT" -echo -e "${GREEN}✓ 前端文件已创建${NC}" - -# 启动前端(后台运行) -export PORT="$FRONTEND_PORT" -nohup python scripts/frontend_server.py > logs/frontend_base.log 2>&1 & - -FRONTEND_PID=$! -echo $FRONTEND_PID > logs/frontend_base.pid -echo -e "${GREEN}前端服务已启动 (PID: $FRONTEND_PID)${NC}" -echo -e "${GREEN}日志文件: logs/frontend_base.log${NC}" - -# 等待前端启动 -echo -e "${YELLOW}等待前端服务启动...${NC}" -MAX_RETRIES=10 -RETRY_COUNT=0 -FRONTEND_READY=false - -while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do - sleep 2 - if curl -s "http://localhost:$FRONTEND_PORT/" > /dev/null 2>&1; then - FRONTEND_READY=true - break - fi - RETRY_COUNT=$((RETRY_COUNT + 1)) - echo -e "${YELLOW} 等待中... ($RETRY_COUNT/$MAX_RETRIES)${NC}" -done - -if [ "$FRONTEND_READY" = true ]; then - echo -e "${GREEN}✓ 前端服务运行正常${NC}" -else - echo -e "${YELLOW}⚠ 前端服务可能还在启动中,请稍后访问${NC}" -fi - -echo -e "\n${GREEN}========================================${NC}" -echo -e "${GREEN}演示环境启动完成!${NC}" -echo -e "${GREEN}========================================${NC}" -echo "" -echo -e "访问地址:" -echo -e " ${GREEN}前端界面: http://localhost:$FRONTEND_PORT/base${NC} (或 http://localhost:$FRONTEND_PORT/base.html)" -echo -e " ${GREEN}后端API: http://localhost:$API_PORT${NC}" -echo -e " ${GREEN}API文档: http://localhost:$API_PORT/docs${NC}" -echo "" -echo -e "配置信息:" -echo -e " Tenant ID: $TENANT_ID" -echo -e " Customer Config: base" -echo "" -echo -e "日志文件:" -echo -e " 后端: logs/backend_base.log" -echo -e " 前端: logs/frontend_base.log" -echo "" -echo -e "停止服务:" -echo -e " 所有服务: ./scripts/stop_base.sh" -echo -e " 单独停止后端: kill \$(cat logs/backend_base.pid)" -echo -e " 单独停止前端: kill \$(cat logs/frontend_base.pid)" -echo "" - diff --git a/scripts/frontend_server.py b/scripts/frontend_server.py index 20ca6d1..58d241a 100755 --- a/scripts/frontend_server.py +++ b/scripts/frontend_server.py @@ -10,6 +10,15 @@ import sys import logging import time from collections import defaultdict, deque +from pathlib import Path +from dotenv import load_dotenv + +# Load .env file +project_root = Path(__file__).parent.parent +load_dotenv(project_root / '.env') + +# Get API_BASE_URL from environment +API_BASE_URL = os.getenv('API_BASE_URL', 'http://localhost:6002') # Change to frontend directory frontend_dir = os.path.join(os.path.dirname(__file__), '../frontend') @@ -46,19 +55,41 @@ class MyHTTPRequestHandler(http.server.SimpleHTTPRequestHandler, RateLimitingMix """Custom request handler with CORS support and robust error handling.""" def do_GET(self): - """Handle GET requests with support for base.html.""" - # Parse path (handle query strings) - path = self.path.split('?')[0] # Remove query string if present + """Handle GET requests with API config injection.""" + path = self.path.split('?')[0] - # Route /base to base.html (handle both with and without trailing slash) - if path == '/base' or path == '/base/': - self.path = '/base.html' + (self.path.split('?', 1)[1] if '?' in self.path else '') - # Route / to index.html (default) - elif path == '/' or path == '': + # Route / to index.html + if path == '/' or path == '': self.path = '/index.html' + (self.path.split('?', 1)[1] if '?' in self.path else '') - # Call parent do_GET with modified path - super().do_GET() + # Inject API config for HTML files + if self.path.endswith('.html'): + self._serve_html_with_config() + else: + super().do_GET() + + def _serve_html_with_config(self): + """Serve HTML with API_BASE_URL injected.""" + try: + file_path = self.path.lstrip('/') + if not os.path.exists(file_path): + self.send_error(404) + return + + with open(file_path, 'r', encoding='utf-8') as f: + html = f.read() + + # Inject API_BASE_URL before app.js + config_script = f'\n ' + html = html.replace('