Commit 1852e3e352053c40124a172ed363ff42e40427e2

Authored by tangwang
1 parent f0577ce4

添加Base配置演示流程和数据库配置

主要变更:
1. 创建.env文件,添加MySQL数据库配置(Shoplazza生产环境)
2. 更新config/env_config.py,添加DB_CONFIG配置
3. 创建demo_base.sh脚本,完整的演示流程:
   - 生成测试数据
   - 导入MySQL
   - 导入Elasticsearch
   - 启动后端服务
   - 启动前端服务
4. 创建create_base_frontend.py,生成base配置专用的前端JS
5. 创建frontend/base.html,base配置专用前端页面
6. 更新frontend_server.py,支持base.html路由和PORT环境变量
7. 创建stop_base.sh,停止演示服务脚本

使用方式:
  bash scripts/demo_base.sh [tenant_id]

访问地址:
  http://localhost:6003/base
1   -# Environment Configuration - ACTIVE
2   -# This file contains the actual configuration values
3   -
4   -# Elasticsearch Configuration (v8.18)
  1 +# Elasticsearch Configuration
5 2 ES_HOST=http://localhost:9200
6   -ES_USERNAME=essa
7   -ES_PASSWORD=4hOaLaf41y2VuI8y
  3 +ES_USERNAME=
  4 +ES_PASSWORD=
8 5  
9   -# Redis Configuration (for caching)
  6 +# Redis Configuration (Optional)
10 7 REDIS_HOST=localhost
11 8 REDIS_PORT=6479
12   -REDIS_PASSWORD=BMfv5aI31kgHWtlx
  9 +REDIS_PASSWORD=
13 10  
14 11 # DeepL Translation API
15   -DEEPL_AUTH_KEY=c9293ab4-ad25-479b-919f-ab4e63b429ed
  12 +DEEPL_AUTH_KEY=
16 13  
17 14 # Customer Configuration
18 15 CUSTOMER_ID=customer1
... ... @@ -21,7 +18,14 @@ CUSTOMER_ID=customer1
21 18 API_HOST=0.0.0.0
22 19 API_PORT=6002
23 20  
24   -# Embedding Models
  21 +# MySQL Database Configuration (Shoplazza)
  22 +DB_HOST=120.79.247.228
  23 +DB_PORT=3316
  24 +DB_DATABASE=saas
  25 +DB_USERNAME=saas
  26 +DB_PASSWORD=P89cZHS5d7dFyc9R
  27 +
  28 +# Model Directories
25 29 TEXT_MODEL_DIR=/data/tw/models/bge-m3
26 30 IMAGE_MODEL_DIR=/data/tw/models/cn-clip
27 31  
... ...
config/env_config.py
... ... @@ -45,6 +45,15 @@ IMAGE_MODEL_DIR = os.getenv('IMAGE_MODEL_DIR', '/data/tw/models/cn-clip')
45 45 # Cache Directory
46 46 CACHE_DIR = os.getenv('CACHE_DIR', '.cache')
47 47  
  48 +# MySQL Database Configuration (Shoplazza)
  49 +DB_CONFIG = {
  50 + 'host': os.getenv('DB_HOST', '120.79.247.228'),
  51 + 'port': int(os.getenv('DB_PORT', 3316)),
  52 + 'database': os.getenv('DB_DATABASE', 'saas'),
  53 + 'username': os.getenv('DB_USERNAME', 'saas'),
  54 + 'password': os.getenv('DB_PASSWORD', 'P89cZHS5d7dFyc9R'),
  55 +}
  56 +
48 57  
49 58 def get_es_config() -> Dict[str, Any]:
50 59 """Get Elasticsearch configuration."""
... ... @@ -66,6 +75,11 @@ def get_customer_id() -> str:
66 75 return CUSTOMER_ID
67 76  
68 77  
  78 +def get_db_config() -> Dict[str, Any]:
  79 + """Get MySQL database configuration."""
  80 + return DB_CONFIG.copy()
  81 +
  82 +
69 83 def print_config():
70 84 """Print current configuration (with sensitive data masked)."""
71 85 print("=" * 60)
... ... @@ -99,6 +113,13 @@ def print_config():
99 113 print("\nCache:")
100 114 print(f" Cache Directory: {CACHE_DIR}")
101 115  
  116 + print("\nMySQL Database:")
  117 + print(f" Host: {DB_CONFIG['host']}")
  118 + print(f" Port: {DB_CONFIG['port']}")
  119 + print(f" Database: {DB_CONFIG['database']}")
  120 + print(f" Username: {DB_CONFIG['username']}")
  121 + print(f" Password: {'*' * 10 if DB_CONFIG['password'] else 'None'}")
  122 +
102 123 print("=" * 60)
103 124  
104 125  
... ...
frontend/base.html 0 → 100644
... ... @@ -0,0 +1,89 @@
  1 +<!DOCTYPE html>
  2 +<html lang="zh-CN">
  3 +<head>
  4 + <meta charset="UTF-8">
  5 + <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6 + <title>店匠通用搜索 - Base Configuration</title>
  7 + <link rel="stylesheet" href="/static/css/style.css">
  8 +</head>
  9 +<body>
  10 + <div class="page-container">
  11 + <!-- Header -->
  12 + <header class="top-header">
  13 + <div class="header-left">
  14 + <span class="logo">Shoplazza Base Search</span>
  15 + <span class="product-count" id="productCount">0 products found</span>
  16 + </div>
  17 + <div class="header-right">
  18 + <button class="fold-btn" onclick="toggleFilters()">Fold</button>
  19 + </div>
  20 + </header>
  21 +
  22 + <!-- Search Bar -->
  23 + <div class="search-bar">
  24 + <input type="text" id="searchInput" placeholder="输入搜索关键词... (支持中文、英文)"
  25 + onkeypress="handleKeyPress(event)">
  26 + <button onclick="performSearch()" class="search-btn">搜索</button>
  27 + </div>
  28 +
  29 + <!-- Filter Section -->
  30 + <div class="filter-section" id="filterSection">
  31 + <!-- Category Filter -->
  32 + <div class="filter-row">
  33 + <div class="filter-label">Categories:</div>
  34 + <div class="filter-tags" id="categoryTags"></div>
  35 + </div>
  36 +
  37 + <!-- Vendor Filter -->
  38 + <div class="filter-row">
  39 + <div class="filter-label">Vendor:</div>
  40 + <div class="filter-tags" id="brandTags"></div>
  41 + </div>
  42 +
  43 + <!-- Tags Filter -->
  44 + <div class="filter-row">
  45 + <div class="filter-label">Tags:</div>
  46 + <div class="filter-tags" id="supplierTags"></div>
  47 + </div>
  48 +
  49 + <!-- Price Range Filter -->
  50 + <div class="filter-row">
  51 + <div class="filter-label">Price Range:</div>
  52 + <div class="filter-tags" id="priceTags"></div>
  53 + </div>
  54 +
  55 + <!-- Clear Filters Button -->
  56 + <div class="filter-row">
  57 + <button id="clearFiltersBtn" onclick="clearAllFilters()" class="clear-filters-btn" style="display: none;">
  58 + Clear All Filters
  59 + </button>
  60 + </div>
  61 + </div>
  62 +
  63 + <!-- Results Section -->
  64 + <div class="results-section">
  65 + <div class="product-grid" id="productGrid">
  66 + <div class="welcome-message">
  67 + <h2>Welcome to Shoplazza Base Search</h2>
  68 + <p>Enter keywords to search for products</p>
  69 + </div>
  70 + </div>
  71 + </div>
  72 +
  73 + <!-- Loading Indicator -->
  74 + <div id="loading" style="display: none; text-align: center; padding: 20px;">
  75 + <div class="spinner"></div>
  76 + <p>Searching...</p>
  77 + </div>
  78 +
  79 + <!-- Debug Info (collapsible) -->
  80 + <div class="debug-section" id="debugSection" style="display: none;">
  81 + <button onclick="toggleDebug()" class="debug-toggle">Toggle Debug Info</button>
  82 + <div id="debugInfo" style="display: none;"></div>
  83 + </div>
  84 + </div>
  85 +
  86 + <script src="/static/js/app_base.js"></script>
  87 +</body>
  88 +</html>
  89 +
... ...
scripts/create_base_frontend.py 0 → 100755
... ... @@ -0,0 +1,218 @@
  1 +#!/usr/bin/env python3
  2 +"""
  3 +Create frontend JavaScript file for base configuration.
  4 +"""
  5 +
  6 +import sys
  7 +import os
  8 +import argparse
  9 +import re
  10 +from pathlib import Path
  11 +
  12 +# Add parent directory to path
  13 +sys.path.insert(0, str(Path(__file__).parent.parent))
  14 +
  15 +
  16 +def create_base_frontend_js(tenant_id: str, api_port: int = 6002, output_file: str = "frontend/static/js/app_base.js"):
  17 + """
  18 + Create frontend JavaScript file for base configuration.
  19 +
  20 + Args:
  21 + tenant_id: Tenant ID
  22 + api_port: API port
  23 + output_file: Output file path
  24 + """
  25 + # Read original app.js
  26 + original_file = Path(__file__).parent.parent / "frontend/static/js/app.js"
  27 + if not original_file.exists():
  28 + print(f"ERROR: Original frontend file not found: {original_file}")
  29 + return 1
  30 +
  31 + with open(original_file, 'r', encoding='utf-8') as f:
  32 + content = f.read()
  33 +
  34 + # Replace API_BASE_URL
  35 + api_url = f"http://localhost:{api_port}"
  36 + content = content.replace(
  37 + "const API_BASE_URL = 'http://120.76.41.98:6002';",
  38 + f"const API_BASE_URL = '{api_url}';"
  39 + )
  40 +
  41 + # Add tenant_id constant at the beginning
  42 + content = content.replace(
  43 + "const API_BASE_URL =",
  44 + f"const TENANT_ID = '{tenant_id}';\nconst API_BASE_URL ="
  45 + )
  46 +
  47 + # Update facets for base configuration
  48 + base_facets = ''' const facets = [
  49 + {
  50 + "field": "category_keyword",
  51 + "size": 15,
  52 + "type": "terms"
  53 + },
  54 + {
  55 + "field": "vendor_keyword",
  56 + "size": 15,
  57 + "type": "terms"
  58 + },
  59 + {
  60 + "field": "tags_keyword",
  61 + "size": 10,
  62 + "type": "terms"
  63 + },
  64 + {
  65 + "field": "min_price",
  66 + "type": "range",
  67 + "ranges": [
  68 + {"key": "0-50", "to": 50},
  69 + {"key": "50-100", "from": 50, "to": 100},
  70 + {"key": "100-200", "from": 100, "to": 200},
  71 + {"key": "200+", "from": 200}
  72 + ]
  73 + }
  74 + ];'''
  75 +
  76 + # Find and replace facets definition (multiline match)
  77 + facets_pattern = r'const facets = \[.*?\];'
  78 + content = re.sub(facets_pattern, base_facets, content, flags=re.DOTALL)
  79 +
  80 + # Update fetch to include tenant_id header
  81 + content = content.replace(
  82 + "headers: {\n 'Content-Type': 'application/json',\n },",
  83 + f"headers: {{\n 'Content-Type': 'application/json',\n 'X-Tenant-ID': TENANT_ID,\n }},"
  84 + )
  85 +
  86 + # Replace hits with results throughout
  87 + content = re.sub(r'\bdata\.hits\b', 'data.results', content)
  88 + content = re.sub(r'!data\.hits', '!data.results', content)
  89 +
  90 + # Replace hit loop with product loop
  91 + content = re.sub(
  92 + r'data\.hits\.forEach\(\(hit\) => \{',
  93 + 'data.results.forEach((product) => {',
  94 + content
  95 + )
  96 +
  97 + # Remove source extraction lines
  98 + content = re.sub(r'const source = hit\._source;\s*\n', '', content)
  99 + content = re.sub(r'const score = hit\._custom_score \|\| hit\._score;\s*\n', 'const score = product.relevance_score;\n', content)
  100 +
  101 + # Replace all source. references with product.
  102 + content = re.sub(r'\bsource\.', 'product.', content)
  103 +
  104 + # Replace specific field names for base configuration
  105 + # imageUrl -> image_url
  106 + content = re.sub(r'product\.imageUrl', 'product.image_url', content)
  107 + # name -> title
  108 + content = re.sub(r'product\.name', 'product.title', content)
  109 + content = re.sub(r'product\.enSpuName', 'product.title', content)
  110 + # categoryName -> category
  111 + content = re.sub(r'product\.categoryName', 'product.category', content)
  112 + # brandName -> vendor
  113 + content = re.sub(r'product\.brandName', 'product.vendor', content)
  114 + # price -> price (already correct)
  115 + # Remove moq and quantity fields (not in base config)
  116 + content = re.sub(r'<div class="product-moq">.*?</div>\s*\n', '', content, flags=re.DOTALL)
  117 + content = re.sub(r'<div class="product-quantity">.*?</div>\s*\n', '', content, flags=re.DOTALL)
  118 +
  119 + # Add stock and variants display
  120 + # Find the product-price div and add stock info after it
  121 + stock_info = ''' <div class="product-stock">
  122 + ${product.in_stock ? '<span style="color: green;">In Stock</span>' : '<span style="color: red;">Out of Stock</span>'}
  123 + ${product.variants && product.variants.length > 0 ? `<span style="color: #666; font-size: 0.9em;">(${product.variants.length} variants)</span>` : ''}
  124 + </div>
  125 +
  126 +'''
  127 + content = re.sub(
  128 + r'(<div class="product-price">.*?</div>\s*\n)',
  129 + r'\1' + stock_info,
  130 + content,
  131 + flags=re.DOTALL
  132 + )
  133 +
  134 + # Update price display format
  135 + content = re.sub(
  136 + r'\$\{product\.price \? `\$\{product\.price\} ₽` : \'N/A\'\}',
  137 + '${product.price ? `$${product.price.toFixed(2)}` : \'N/A\'}',
  138 + content
  139 + )
  140 +
  141 + # Add compare_at_price if exists
  142 + content = re.sub(
  143 + r'(\$\{product\.price \? `\$\$\{product\.price\.toFixed\(2\)\}` : \'N/A\'\})',
  144 + r'\1${product.compare_at_price && product.compare_at_price > product.price ? `<span style="text-decoration: line-through; color: #999; font-size: 0.9em; margin-left: 8px;">$${product.compare_at_price.toFixed(2)}</span>` : \'\'}',
  145 + content
  146 + )
  147 +
  148 + # Update product-meta to use base config fields
  149 + content = re.sub(
  150 + r'<div class="product-meta">\s*\$\{product\.category \? escapeHtml\(product\.category\) : \'\'\}\s*\$\{product\.vendor \? \' \| \' \+ escapeHtml\(product\.vendor\) : \'\'\}\s*</div>',
  151 + '<div class="product-meta">${product.vendor ? escapeHtml(product.vendor) : \'\'}${product.product_type ? \' | \' + escapeHtml(product.product_type) : \'\'}${product.category ? \' | \' + escapeHtml(product.category) : \'\'}</div>',
  152 + content
  153 + )
  154 +
  155 + # Remove create_time display (not in base config)
  156 + content = re.sub(
  157 + r'\$\{product\.create_time \? `.*?</div>\s*` : \'\'\}',
  158 + '',
  159 + content,
  160 + flags=re.DOTALL
  161 + )
  162 +
  163 + # Add tags display if exists
  164 + tags_display = ''' ${product.tags ? `
  165 + <div class="product-tags">
  166 + Tags: ${escapeHtml(product.tags)}
  167 + </div>
  168 + ` : ''}'''
  169 +
  170 + # Add tags before closing product-card div
  171 + content = re.sub(
  172 + r'(</div>\s*</div>\s*`;\s*\n\s*\}\);)',
  173 + tags_display + r'\n </div>\n `;\n });',
  174 + content,
  175 + count=1
  176 + )
  177 +
  178 + # Update displayFacets for base configuration field names
  179 + content = re.sub(
  180 + r"facet\.field === 'categoryName_keyword'",
  181 + "facet.field === 'category_keyword'",
  182 + content
  183 + )
  184 + content = re.sub(
  185 + r"facet\.field === 'brandName_keyword'",
  186 + "facet.field === 'vendor_keyword'",
  187 + content
  188 + )
  189 + content = re.sub(
  190 + r"facet\.field === 'supplierName_keyword'",
  191 + "facet.field === 'tags_keyword'",
  192 + content
  193 + )
  194 +
  195 + # Write output file
  196 + output_path = Path(__file__).parent.parent / output_file
  197 + output_path.parent.mkdir(parents=True, exist_ok=True)
  198 +
  199 + with open(output_path, 'w', encoding='utf-8') as f:
  200 + f.write(content)
  201 +
  202 + print(f"Created base frontend JavaScript: {output_path}")
  203 + return 0
  204 +
  205 +
  206 +def main():
  207 + parser = argparse.ArgumentParser(description='Create frontend JavaScript for base configuration')
  208 + parser.add_argument('--tenant-id', default='1', help='Tenant ID')
  209 + parser.add_argument('--api-port', type=int, default=6002, help='API port')
  210 + parser.add_argument('--output', default='frontend/static/js/app_base.js', help='Output file')
  211 +
  212 + args = parser.parse_args()
  213 +
  214 + return create_base_frontend_js(args.tenant_id, args.api_port, args.output)
  215 +
  216 +
  217 +if __name__ == '__main__':
  218 + sys.exit(main())
... ...
scripts/demo_base.sh 0 → 100755
... ... @@ -0,0 +1,198 @@
  1 +#!/bin/bash
  2 +
  3 +# Base配置演示流程脚本
  4 +# 用于演示店匠通用客户的搜索效果
  5 +
  6 +set -e
  7 +
  8 +cd "$(dirname "$0")/.."
  9 +source /home/tw/miniconda3/etc/profile.d/conda.sh
  10 +conda activate searchengine
  11 +
  12 +GREEN='\033[0;32m'
  13 +YELLOW='\033[1;33m'
  14 +RED='\033[0;31m'
  15 +NC='\033[0m'
  16 +
  17 +echo -e "${GREEN}========================================${NC}"
  18 +echo -e "${GREEN}Base配置演示流程${NC}"
  19 +echo -e "${GREEN}========================================${NC}"
  20 +
  21 +# 加载.env配置文件
  22 +if [ -f .env ]; then
  23 + set -a
  24 + source .env
  25 + set +a
  26 +fi
  27 +
  28 +# 配置参数(从环境变量或默认值)
  29 +TENANT_ID=${1:-"1"}
  30 +DB_HOST=${DB_HOST:-"120.79.247.228"}
  31 +DB_PORT=${DB_PORT:-"3316"}
  32 +DB_DATABASE=${DB_DATABASE:-"saas"}
  33 +DB_USERNAME=${DB_USERNAME:-"saas"}
  34 +DB_PASSWORD=${DB_PASSWORD:-"P89cZHS5d7dFyc9R"}
  35 +ES_HOST=${ES_HOST:-"http://localhost:9200"}
  36 +API_PORT=${API_PORT:-"6002"}
  37 +FRONTEND_PORT=${FRONTEND_PORT:-"6003"}
  38 +
  39 +echo -e "\n${YELLOW}配置参数:${NC}"
  40 +echo " Tenant ID: $TENANT_ID"
  41 +echo " MySQL: $DB_HOST:$DB_PORT/$DB_DATABASE"
  42 +echo " Elasticsearch: $ES_HOST"
  43 +echo " API Port: $API_PORT"
  44 +echo " Frontend Port: $FRONTEND_PORT"
  45 +
  46 +# Step 1: 生成测试数据
  47 +echo -e "\n${YELLOW}Step 1/5: 生成测试数据${NC}"
  48 +if [ ! -f "test_data_base.sql" ]; then
  49 + echo "生成100条SPU测试数据..."
  50 + python scripts/generate_test_data.py \
  51 + --num-spus 100 \
  52 + --tenant-id "$TENANT_ID" \
  53 + --start-spu-id 1 \
  54 + --start-sku-id 1 \
  55 + --output test_data_base.sql
  56 + echo -e "${GREEN}✓ 测试数据已生成: test_data_base.sql${NC}"
  57 +else
  58 + echo -e "${YELLOW}⚠ 测试数据文件已存在,跳过生成${NC}"
  59 +fi
  60 +
  61 +# Step 2: 导入测试数据到MySQL
  62 +echo -e "\n${YELLOW}Step 2/5: 导入测试数据到MySQL${NC}"
  63 +if [ -z "$DB_PASSWORD" ]; then
  64 + echo -e "${RED}ERROR: DB_PASSWORD未设置,请检查.env文件或环境变量${NC}"
  65 + exit 1
  66 +fi
  67 +
  68 +python scripts/import_test_data.py \
  69 + --db-host "$DB_HOST" \
  70 + --db-port "$DB_PORT" \
  71 + --db-database "$DB_DATABASE" \
  72 + --db-username "$DB_USERNAME" \
  73 + --db-password "$DB_PASSWORD" \
  74 + --sql-file test_data_base.sql \
  75 + --tenant-id "$TENANT_ID"
  76 +
  77 +echo -e "${GREEN}✓ 测试数据已导入MySQL${NC}"
  78 +
  79 +# Step 3: 导入数据到Elasticsearch
  80 +echo -e "\n${YELLOW}Step 3/5: 导入数据到Elasticsearch${NC}"
  81 +python scripts/ingest_shoplazza.py \
  82 + --db-host "$DB_HOST" \
  83 + --db-port "$DB_PORT" \
  84 + --db-database "$DB_DATABASE" \
  85 + --db-username "$DB_USERNAME" \
  86 + --db-password "$DB_PASSWORD" \
  87 + --tenant-id "$TENANT_ID" \
  88 + --config base \
  89 + --es-host "$ES_HOST" \
  90 + --recreate \
  91 + --batch-size 500
  92 +
  93 +echo -e "${GREEN}✓ 数据已导入Elasticsearch${NC}"
  94 +
  95 +# Step 4: 启动后端服务
  96 +echo -e "\n${YELLOW}Step 4/5: 启动后端服务${NC}"
  97 +echo "后端服务将在后台运行..."
  98 +
  99 +# 创建logs目录
  100 +mkdir -p logs
  101 +
  102 +# 启动后端(后台运行)
  103 +nohup python -m api.app \
  104 + --host 0.0.0.0 \
  105 + --port "$API_PORT" \
  106 + --customer base \
  107 + --es-host "$ES_HOST" > logs/backend_base.log 2>&1 &
  108 +
  109 +BACKEND_PID=$!
  110 +echo $BACKEND_PID > logs/backend_base.pid
  111 +echo -e "${GREEN}后端服务已启动 (PID: $BACKEND_PID)${NC}"
  112 +echo -e "${GREEN}日志文件: logs/backend_base.log${NC}"
  113 +
  114 +# 等待后端启动
  115 +echo -e "${YELLOW}等待后端服务启动...${NC}"
  116 +MAX_RETRIES=15
  117 +RETRY_COUNT=0
  118 +BACKEND_READY=false
  119 +
  120 +while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
  121 + sleep 2
  122 + if curl -s "http://localhost:$API_PORT/health" > /dev/null 2>&1; then
  123 + BACKEND_READY=true
  124 + break
  125 + fi
  126 + RETRY_COUNT=$((RETRY_COUNT + 1))
  127 + echo -e "${YELLOW} 等待中... ($RETRY_COUNT/$MAX_RETRIES)${NC}"
  128 +done
  129 +
  130 +if [ "$BACKEND_READY" = true ]; then
  131 + echo -e "${GREEN}✓ 后端服务运行正常${NC}"
  132 +else
  133 + echo -e "${YELLOW}⚠ 后端服务可能还在启动中,请稍后访问${NC}"
  134 +fi
  135 +
  136 +# Step 5: 启动前端服务
  137 +echo -e "\n${YELLOW}Step 5/5: 启动前端服务${NC}"
  138 +echo "前端服务将在后台运行..."
  139 +
  140 +# 创建base配置的前端JS文件
  141 +echo "创建base配置前端文件..."
  142 +python scripts/create_base_frontend.py --tenant-id "$TENANT_ID" --api-port "$API_PORT"
  143 +echo -e "${GREEN}✓ 前端文件已创建${NC}"
  144 +
  145 +# 启动前端(后台运行)
  146 +export PORT="$FRONTEND_PORT"
  147 +nohup python scripts/frontend_server.py > logs/frontend_base.log 2>&1 &
  148 +
  149 +FRONTEND_PID=$!
  150 +echo $FRONTEND_PID > logs/frontend_base.pid
  151 +echo -e "${GREEN}前端服务已启动 (PID: $FRONTEND_PID)${NC}"
  152 +echo -e "${GREEN}日志文件: logs/frontend_base.log${NC}"
  153 +
  154 +# 等待前端启动
  155 +echo -e "${YELLOW}等待前端服务启动...${NC}"
  156 +MAX_RETRIES=10
  157 +RETRY_COUNT=0
  158 +FRONTEND_READY=false
  159 +
  160 +while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
  161 + sleep 2
  162 + if curl -s "http://localhost:$FRONTEND_PORT/" > /dev/null 2>&1; then
  163 + FRONTEND_READY=true
  164 + break
  165 + fi
  166 + RETRY_COUNT=$((RETRY_COUNT + 1))
  167 + echo -e "${YELLOW} 等待中... ($RETRY_COUNT/$MAX_RETRIES)${NC}"
  168 +done
  169 +
  170 +if [ "$FRONTEND_READY" = true ]; then
  171 + echo -e "${GREEN}✓ 前端服务运行正常${NC}"
  172 +else
  173 + echo -e "${YELLOW}⚠ 前端服务可能还在启动中,请稍后访问${NC}"
  174 +fi
  175 +
  176 +echo -e "\n${GREEN}========================================${NC}"
  177 +echo -e "${GREEN}演示环境启动完成!${NC}"
  178 +echo -e "${GREEN}========================================${NC}"
  179 +echo ""
  180 +echo -e "访问地址:"
  181 +echo -e " ${GREEN}前端界面: http://localhost:$FRONTEND_PORT/base${NC}"
  182 +echo -e " ${GREEN}后端API: http://localhost:$API_PORT${NC}"
  183 +echo -e " ${GREEN}API文档: http://localhost:$API_PORT/docs${NC}"
  184 +echo ""
  185 +echo -e "配置信息:"
  186 +echo -e " Tenant ID: $TENANT_ID"
  187 +echo -e " Customer Config: base"
  188 +echo ""
  189 +echo -e "日志文件:"
  190 +echo -e " 后端: logs/backend_base.log"
  191 +echo -e " 前端: logs/frontend_base.log"
  192 +echo ""
  193 +echo -e "停止服务:"
  194 +echo -e " 所有服务: ./scripts/stop_base.sh"
  195 +echo -e " 单独停止后端: kill \$(cat logs/backend_base.pid)"
  196 +echo -e " 单独停止前端: kill \$(cat logs/frontend_base.pid)"
  197 +echo ""
  198 +
... ...
scripts/frontend_server.py
... ... @@ -15,7 +15,8 @@ from collections import defaultdict, deque
15 15 frontend_dir = os.path.join(os.path.dirname(__file__), '../frontend')
16 16 os.chdir(frontend_dir)
17 17  
18   -PORT = 6003
  18 +# Get port from environment variable or default
  19 +PORT = int(os.getenv('PORT', 6003))
19 20  
20 21 # Configure logging to suppress scanner noise
21 22 logging.basicConfig(level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s')
... ... @@ -44,6 +45,16 @@ class RateLimitingMixin:
44 45 class MyHTTPRequestHandler(http.server.SimpleHTTPRequestHandler, RateLimitingMixin):
45 46 """Custom request handler with CORS support and robust error handling."""
46 47  
  48 + def do_GET(self):
  49 + """Handle GET requests with support for base.html."""
  50 + # Route /base to base.html
  51 + if self.path == '/base' or self.path == '/base/':
  52 + self.path = '/base.html'
  53 + # Route / to index.html (default)
  54 + elif self.path == '/':
  55 + self.path = '/index.html'
  56 + return super().do_GET()
  57 +
47 58 def setup(self):
48 59 """Setup with error handling."""
49 60 try:
... ...
scripts/stop_base.sh 0 → 100755
... ... @@ -0,0 +1,45 @@
  1 +#!/bin/bash
  2 +
  3 +# Stop Base配置演示服务
  4 +
  5 +set -e
  6 +
  7 +cd "$(dirname "$0")/.."
  8 +
  9 +GREEN='\033[0;32m'
  10 +YELLOW='\033[1;33m'
  11 +RED='\033[0;31m'
  12 +NC='\033[0m'
  13 +
  14 +echo -e "${YELLOW}停止Base配置演示服务...${NC}"
  15 +
  16 +# Stop backend
  17 +if [ -f logs/backend_base.pid ]; then
  18 + BACKEND_PID=$(cat logs/backend_base.pid)
  19 + if ps -p $BACKEND_PID > /dev/null 2>&1; then
  20 + kill $BACKEND_PID
  21 + echo -e "${GREEN}✓ 后端服务已停止 (PID: $BACKEND_PID)${NC}"
  22 + else
  23 + echo -e "${YELLOW}⚠ 后端服务进程不存在${NC}"
  24 + fi
  25 + rm -f logs/backend_base.pid
  26 +else
  27 + echo -e "${YELLOW}⚠ 后端服务PID文件不存在${NC}"
  28 +fi
  29 +
  30 +# Stop frontend
  31 +if [ -f logs/frontend_base.pid ]; then
  32 + FRONTEND_PID=$(cat logs/frontend_base.pid)
  33 + if ps -p $FRONTEND_PID > /dev/null 2>&1; then
  34 + kill $FRONTEND_PID
  35 + echo -e "${GREEN}✓ 前端服务已停止 (PID: $FRONTEND_PID)${NC}"
  36 + else
  37 + echo -e "${YELLOW}⚠ 前端服务进程不存在${NC}"
  38 + fi
  39 + rm -f logs/frontend_base.pid
  40 +else
  41 + echo -e "${YELLOW}⚠ 前端服务PID文件不存在${NC}"
  42 +fi
  43 +
  44 +echo -e "${GREEN}所有服务已停止${NC}"
  45 +
... ...