Commit 1852e3e352053c40124a172ed363ff42e40427e2
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
Showing
7 changed files
with
597 additions
and
11 deletions
Show diff stats
| 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 | ... | ... |
| ... | ... | @@ -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 | + | ... | ... |
| ... | ... | @@ -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()) | ... | ... |
| ... | ... | @@ -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: | ... | ... |
| ... | ... | @@ -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 | + | ... | ... |