Commit 362d43b67e9a728680610a07147a27db33adf256
1 parent
4d824a77
店匠体系数据的搜索
Showing
11 changed files
with
54 additions
and
1113 deletions
Show diff stats
config/env_config.py
| ... | ... | @@ -37,6 +37,10 @@ CUSTOMER_ID = os.getenv('CUSTOMER_ID', 'customer1') |
| 37 | 37 | # API Service Configuration |
| 38 | 38 | API_HOST = os.getenv('API_HOST', '0.0.0.0') |
| 39 | 39 | API_PORT = int(os.getenv('API_PORT', 6002)) |
| 40 | +# API_BASE_URL: 如果未设置,根据API_HOST构建(0.0.0.0使用localhost) | |
| 41 | +API_BASE_URL = os.getenv('API_BASE_URL') | |
| 42 | +if not API_BASE_URL: | |
| 43 | + API_BASE_URL = f'http://localhost:{API_PORT}' if API_HOST == '0.0.0.0' else f'http://{API_HOST}:{API_PORT}' | |
| 40 | 44 | |
| 41 | 45 | # Model Directories |
| 42 | 46 | TEXT_MODEL_DIR = os.getenv('TEXT_MODEL_DIR', '/data/tw/models/bge-m3') | ... | ... |
config/schema/base/config.yaml deleted
| ... | ... | @@ -1,271 +0,0 @@ |
| 1 | -# Base Configuration for Shoplazza | |
| 2 | -# 店匠通用配置文件,所有使用店匠表的客户共用 | |
| 3 | -# 注意:此配置不包含MySQL相关配置,只包含ES搜索相关配置 | |
| 4 | - | |
| 5 | -customer_name: "Shoplazza Base Configuration" | |
| 6 | - | |
| 7 | -# Elasticsearch Index | |
| 8 | -es_index_name: "search_products" | |
| 9 | - | |
| 10 | -# ES Index Settings | |
| 11 | -es_settings: | |
| 12 | - number_of_shards: 1 | |
| 13 | - number_of_replicas: 0 | |
| 14 | - refresh_interval: "30s" | |
| 15 | - | |
| 16 | -# Field Definitions (SPU级别,只包含对搜索有帮助的字段) | |
| 17 | -fields: | |
| 18 | - # 租户隔离字段(必需) | |
| 19 | - - name: "tenant_id" | |
| 20 | - type: "KEYWORD" | |
| 21 | - required: true | |
| 22 | - index: true | |
| 23 | - store: true | |
| 24 | - | |
| 25 | - # 商品标识字段 | |
| 26 | - - name: "product_id" | |
| 27 | - type: "KEYWORD" | |
| 28 | - required: true | |
| 29 | - index: true | |
| 30 | - store: true | |
| 31 | - | |
| 32 | - - name: "handle" | |
| 33 | - type: "KEYWORD" | |
| 34 | - index: true | |
| 35 | - store: true | |
| 36 | - | |
| 37 | - # 文本搜索字段 | |
| 38 | - - name: "title" | |
| 39 | - type: "TEXT" | |
| 40 | - analyzer: "chinese_ecommerce" | |
| 41 | - boost: 3.0 | |
| 42 | - index: true | |
| 43 | - store: true | |
| 44 | - | |
| 45 | - - name: "brief" | |
| 46 | - type: "TEXT" | |
| 47 | - analyzer: "chinese_ecommerce" | |
| 48 | - boost: 1.5 | |
| 49 | - index: true | |
| 50 | - store: true | |
| 51 | - | |
| 52 | - - name: "description" | |
| 53 | - type: "TEXT" | |
| 54 | - analyzer: "chinese_ecommerce" | |
| 55 | - boost: 1.0 | |
| 56 | - index: true | |
| 57 | - store: true | |
| 58 | - | |
| 59 | - # SEO字段(提升相关性) | |
| 60 | - - name: "seo_title" | |
| 61 | - type: "TEXT" | |
| 62 | - analyzer: "chinese_ecommerce" | |
| 63 | - boost: 2.0 | |
| 64 | - index: true | |
| 65 | - store: true | |
| 66 | - | |
| 67 | - - name: "seo_description" | |
| 68 | - type: "TEXT" | |
| 69 | - analyzer: "chinese_ecommerce" | |
| 70 | - boost: 1.5 | |
| 71 | - index: true | |
| 72 | - store: true | |
| 73 | - | |
| 74 | - - name: "seo_keywords" | |
| 75 | - type: "TEXT" | |
| 76 | - analyzer: "chinese_ecommerce" | |
| 77 | - boost: 2.0 | |
| 78 | - index: true | |
| 79 | - store: true | |
| 80 | - | |
| 81 | - # 分类和标签字段(TEXT + KEYWORD双重索引) | |
| 82 | - - name: "vendor" | |
| 83 | - type: "TEXT" | |
| 84 | - analyzer: "chinese_ecommerce" | |
| 85 | - boost: 1.5 | |
| 86 | - index: true | |
| 87 | - store: true | |
| 88 | - | |
| 89 | - - name: "vendor_keyword" | |
| 90 | - type: "KEYWORD" | |
| 91 | - index: true | |
| 92 | - store: false | |
| 93 | - | |
| 94 | - - name: "product_type" | |
| 95 | - type: "TEXT" | |
| 96 | - analyzer: "chinese_ecommerce" | |
| 97 | - boost: 1.5 | |
| 98 | - index: true | |
| 99 | - store: true | |
| 100 | - | |
| 101 | - - name: "product_type_keyword" | |
| 102 | - type: "KEYWORD" | |
| 103 | - index: true | |
| 104 | - store: false | |
| 105 | - | |
| 106 | - - name: "tags" | |
| 107 | - type: "TEXT" | |
| 108 | - analyzer: "chinese_ecommerce" | |
| 109 | - boost: 1.0 | |
| 110 | - index: true | |
| 111 | - store: true | |
| 112 | - | |
| 113 | - - name: "tags_keyword" | |
| 114 | - type: "KEYWORD" | |
| 115 | - index: true | |
| 116 | - store: false | |
| 117 | - | |
| 118 | - - name: "category" | |
| 119 | - type: "TEXT" | |
| 120 | - analyzer: "chinese_ecommerce" | |
| 121 | - boost: 1.5 | |
| 122 | - index: true | |
| 123 | - store: true | |
| 124 | - | |
| 125 | - - name: "category_keyword" | |
| 126 | - type: "KEYWORD" | |
| 127 | - index: true | |
| 128 | - store: false | |
| 129 | - | |
| 130 | - # 价格字段(扁平化) | |
| 131 | - - name: "min_price" | |
| 132 | - type: "FLOAT" | |
| 133 | - index: true | |
| 134 | - store: true | |
| 135 | - | |
| 136 | - - name: "max_price" | |
| 137 | - type: "FLOAT" | |
| 138 | - index: true | |
| 139 | - store: true | |
| 140 | - | |
| 141 | - - name: "compare_at_price" | |
| 142 | - type: "FLOAT" | |
| 143 | - index: true | |
| 144 | - store: true | |
| 145 | - | |
| 146 | - # 图片字段(用于显示,不参与搜索) | |
| 147 | - - name: "image_url" | |
| 148 | - type: "KEYWORD" | |
| 149 | - index: false | |
| 150 | - store: true | |
| 151 | - | |
| 152 | - # 嵌套variants字段 | |
| 153 | - - name: "variants" | |
| 154 | - type: "JSON" | |
| 155 | - nested: true | |
| 156 | - nested_properties: | |
| 157 | - variant_id: | |
| 158 | - type: "keyword" | |
| 159 | - index: true | |
| 160 | - store: true | |
| 161 | - title: | |
| 162 | - type: "text" | |
| 163 | - analyzer: "chinese_ecommerce" | |
| 164 | - index: true | |
| 165 | - store: true | |
| 166 | - price: | |
| 167 | - type: "float" | |
| 168 | - index: true | |
| 169 | - store: true | |
| 170 | - compare_at_price: | |
| 171 | - type: "float" | |
| 172 | - index: true | |
| 173 | - store: true | |
| 174 | - sku: | |
| 175 | - type: "keyword" | |
| 176 | - index: true | |
| 177 | - store: true | |
| 178 | - stock: | |
| 179 | - type: "long" | |
| 180 | - index: true | |
| 181 | - store: true | |
| 182 | - options: | |
| 183 | - type: "object" | |
| 184 | - enabled: true | |
| 185 | - | |
| 186 | -# Index Structure (Query Domains) | |
| 187 | -indexes: | |
| 188 | - - name: "default" | |
| 189 | - label: "默认索引" | |
| 190 | - fields: | |
| 191 | - - "title" | |
| 192 | - - "brief" | |
| 193 | - - "description" | |
| 194 | - - "seo_title" | |
| 195 | - - "seo_description" | |
| 196 | - - "seo_keywords" | |
| 197 | - - "vendor" | |
| 198 | - - "product_type" | |
| 199 | - - "tags" | |
| 200 | - - "category" | |
| 201 | - analyzer: "chinese_ecommerce" | |
| 202 | - boost: 1.0 | |
| 203 | - | |
| 204 | - - name: "title" | |
| 205 | - label: "标题索引" | |
| 206 | - fields: | |
| 207 | - - "title" | |
| 208 | - - "seo_title" | |
| 209 | - analyzer: "chinese_ecommerce" | |
| 210 | - boost: 2.0 | |
| 211 | - | |
| 212 | - - name: "vendor" | |
| 213 | - label: "品牌索引" | |
| 214 | - fields: | |
| 215 | - - "vendor" | |
| 216 | - analyzer: "chinese_ecommerce" | |
| 217 | - boost: 1.5 | |
| 218 | - | |
| 219 | - - name: "category" | |
| 220 | - label: "类目索引" | |
| 221 | - fields: | |
| 222 | - - "category" | |
| 223 | - analyzer: "chinese_ecommerce" | |
| 224 | - boost: 1.5 | |
| 225 | - | |
| 226 | - - name: "tags" | |
| 227 | - label: "标签索引" | |
| 228 | - fields: | |
| 229 | - - "tags" | |
| 230 | - - "seo_keywords" | |
| 231 | - analyzer: "chinese_ecommerce" | |
| 232 | - boost: 1.0 | |
| 233 | - | |
| 234 | -# Query Configuration | |
| 235 | -query_config: | |
| 236 | - supported_languages: | |
| 237 | - - "zh" | |
| 238 | - - "en" | |
| 239 | - default_language: "zh" | |
| 240 | - enable_translation: true | |
| 241 | - enable_text_embedding: true | |
| 242 | - enable_query_rewrite: true | |
| 243 | - | |
| 244 | - # Translation API (DeepL) | |
| 245 | - translation_service: "deepl" | |
| 246 | - translation_api_key: null # Set via environment variable | |
| 247 | - | |
| 248 | -# Ranking Configuration | |
| 249 | -ranking: | |
| 250 | - expression: "bm25() + 0.2*text_embedding_relevance()" | |
| 251 | - description: "BM25 text relevance combined with semantic embedding similarity" | |
| 252 | - | |
| 253 | -# Function Score配置(ES层打分规则) | |
| 254 | -function_score: | |
| 255 | - score_mode: "sum" | |
| 256 | - boost_mode: "multiply" | |
| 257 | - | |
| 258 | - functions: [] | |
| 259 | - | |
| 260 | -# Rerank配置(本地重排,当前禁用) | |
| 261 | -rerank: | |
| 262 | - enabled: false | |
| 263 | - expression: "" | |
| 264 | - description: "Local reranking (disabled, use ES function_score instead)" | |
| 265 | - | |
| 266 | -# SPU配置(已启用,使用嵌套variants) | |
| 267 | -spu_config: | |
| 268 | - enabled: true | |
| 269 | - spu_field: "product_id" | |
| 270 | - inner_hits_size: 10 | |
| 271 | - |
config/schema/customer1/config.yaml deleted
| ... | ... | @@ -1,363 +0,0 @@ |
| 1 | -# Customer1 Configuration | |
| 2 | -# Test customer for cross-border e-commerce search | |
| 3 | - | |
| 4 | -customer_name: "Customer1 Test Instance" | |
| 5 | - | |
| 6 | -# MySQL Database Configuration | |
| 7 | -mysql_config: | |
| 8 | - host: "120.79.247.228" | |
| 9 | - port: 3316 | |
| 10 | - database: "saas" | |
| 11 | - username: "saas" | |
| 12 | - password: "P89cZHS5d7dFyc9R" | |
| 13 | - | |
| 14 | -# Table Configuration | |
| 15 | -main_table: "shoplazza_product_sku" | |
| 16 | -extension_table: "customer1_extension" | |
| 17 | - | |
| 18 | -# Elasticsearch Index | |
| 19 | -es_index_name: "search_customer1" | |
| 20 | - | |
| 21 | -# ES Index Settings | |
| 22 | -es_settings: | |
| 23 | - number_of_shards: 1 | |
| 24 | - number_of_replicas: 0 | |
| 25 | - refresh_interval: "30s" | |
| 26 | - | |
| 27 | -# Field Definitions | |
| 28 | -fields: | |
| 29 | - # Primary Key | |
| 30 | - - name: "skuId" | |
| 31 | - type: "LONG" | |
| 32 | - source_table: "main" | |
| 33 | - source_column: "id" | |
| 34 | - required: true | |
| 35 | - index: true | |
| 36 | - store: true | |
| 37 | - | |
| 38 | - # Text Fields - Multi-language | |
| 39 | - - name: "name" | |
| 40 | - type: "TEXT" | |
| 41 | - source_table: "extension" | |
| 42 | - source_column: "name" | |
| 43 | - analyzer: "chinese_ecommerce" | |
| 44 | - boost: 2.0 | |
| 45 | - index: true | |
| 46 | - store: true | |
| 47 | - | |
| 48 | - - name: "name_pinyin" | |
| 49 | - type: "TEXT" | |
| 50 | - source_table: "extension" | |
| 51 | - source_column: "name_pinyin" | |
| 52 | - analyzer: "standard" | |
| 53 | - boost: 1.5 | |
| 54 | - index: true | |
| 55 | - store: false | |
| 56 | - | |
| 57 | - - name: "ruSkuName" | |
| 58 | - type: "TEXT" | |
| 59 | - source_table: "extension" | |
| 60 | - source_column: "ruSkuName" | |
| 61 | - analyzer: "russian" | |
| 62 | - boost: 2.0 | |
| 63 | - index: true | |
| 64 | - store: true | |
| 65 | - | |
| 66 | - - name: "enSpuName" | |
| 67 | - type: "TEXT" | |
| 68 | - source_table: "extension" | |
| 69 | - source_column: "enSpuName" | |
| 70 | - analyzer: "english" | |
| 71 | - boost: 2.0 | |
| 72 | - index: true | |
| 73 | - store: true | |
| 74 | - | |
| 75 | - # Category and Brand | |
| 76 | - - name: "categoryName" | |
| 77 | - type: "TEXT" | |
| 78 | - source_table: "extension" | |
| 79 | - source_column: "categoryName" | |
| 80 | - analyzer: "chinese_ecommerce" | |
| 81 | - boost: 1.5 | |
| 82 | - index: true | |
| 83 | - store: true | |
| 84 | - | |
| 85 | - - name: "brandName" | |
| 86 | - type: "TEXT" | |
| 87 | - source_table: "extension" | |
| 88 | - source_column: "brandName" | |
| 89 | - analyzer: "chinese_ecommerce" | |
| 90 | - boost: 1.5 | |
| 91 | - index: true | |
| 92 | - store: true | |
| 93 | - | |
| 94 | - - name: "supplierName" | |
| 95 | - type: "TEXT" | |
| 96 | - source_table: "extension" | |
| 97 | - source_column: "supplierName" | |
| 98 | - analyzer: "chinese_ecommerce" | |
| 99 | - boost: 1.0 | |
| 100 | - index: true | |
| 101 | - store: true | |
| 102 | - | |
| 103 | - # Keyword Fields | |
| 104 | - - name: "categoryName_keyword" | |
| 105 | - type: "KEYWORD" | |
| 106 | - source_table: "extension" | |
| 107 | - source_column: "categoryName" | |
| 108 | - index: true | |
| 109 | - store: false | |
| 110 | - | |
| 111 | - - name: "brandName_keyword" | |
| 112 | - type: "KEYWORD" | |
| 113 | - source_table: "extension" | |
| 114 | - source_column: "brandName" | |
| 115 | - index: true | |
| 116 | - store: false | |
| 117 | - | |
| 118 | - - name: "supplierName_keyword" | |
| 119 | - type: "KEYWORD" | |
| 120 | - source_table: "extension" | |
| 121 | - source_column: "supplierName" | |
| 122 | - index: true | |
| 123 | - store: false | |
| 124 | - | |
| 125 | - # Price Fields | |
| 126 | - - name: "price" | |
| 127 | - type: "DOUBLE" | |
| 128 | - source_table: "extension" | |
| 129 | - source_column: "price" | |
| 130 | - index: true | |
| 131 | - store: true | |
| 132 | - | |
| 133 | - # Text Embedding Fields | |
| 134 | - - name: "name_embedding" | |
| 135 | - type: "TEXT_EMBEDDING" | |
| 136 | - source_table: "extension" | |
| 137 | - source_column: "name" | |
| 138 | - embedding_dims: 1024 | |
| 139 | - embedding_similarity: "dot_product" | |
| 140 | - index: true | |
| 141 | - | |
| 142 | - - name: "enSpuName_embedding" | |
| 143 | - type: "TEXT_EMBEDDING" | |
| 144 | - source_table: "extension" | |
| 145 | - source_column: "enSpuName" | |
| 146 | - embedding_dims: 1024 | |
| 147 | - embedding_similarity: "dot_product" | |
| 148 | - index: true | |
| 149 | - | |
| 150 | - # Image Fields | |
| 151 | - - name: "imageUrl" | |
| 152 | - type: "KEYWORD" | |
| 153 | - source_table: "extension" | |
| 154 | - source_column: "imageUrl" | |
| 155 | - index: true | |
| 156 | - store: true | |
| 157 | - | |
| 158 | - - name: "image_embedding" | |
| 159 | - type: "IMAGE_EMBEDDING" | |
| 160 | - source_table: "extension" | |
| 161 | - source_column: "imageUrl" | |
| 162 | - embedding_dims: 1024 | |
| 163 | - embedding_similarity: "dot_product" | |
| 164 | - nested: false | |
| 165 | - index: true | |
| 166 | - | |
| 167 | - # Metadata Fields | |
| 168 | - - name: "create_time" | |
| 169 | - type: "DATE" | |
| 170 | - source_table: "extension" | |
| 171 | - source_column: "create_time" | |
| 172 | - index: true | |
| 173 | - store: true | |
| 174 | - | |
| 175 | - - name: "days_since_last_update" | |
| 176 | - type: "INT" | |
| 177 | - source_table: "extension" | |
| 178 | - source_column: "days_since_last_update" | |
| 179 | - index: true | |
| 180 | - store: true | |
| 181 | - | |
| 182 | -# Index Structure (Query Domains) | |
| 183 | -indexes: | |
| 184 | - - name: "default" | |
| 185 | - label: "默认索引" | |
| 186 | - fields: | |
| 187 | - - "name" | |
| 188 | - - "enSpuName" | |
| 189 | - - "ruSkuName" | |
| 190 | - - "categoryName" | |
| 191 | - - "brandName" | |
| 192 | - analyzer: "chinese_ecommerce" | |
| 193 | - boost: 1.0 | |
| 194 | - example: 'query=default:"消防套"' | |
| 195 | - language_field_mapping: | |
| 196 | - zh: | |
| 197 | - - "name" | |
| 198 | - - "categoryName" | |
| 199 | - - "brandName" | |
| 200 | - en: | |
| 201 | - - "enSpuName" | |
| 202 | - ru: | |
| 203 | - - "ruSkuName" | |
| 204 | - | |
| 205 | - - name: "title" | |
| 206 | - label: "标题索引" | |
| 207 | - fields: | |
| 208 | - - "name" | |
| 209 | - - "enSpuName" | |
| 210 | - - "ruSkuName" | |
| 211 | - analyzer: "chinese_ecommerce" | |
| 212 | - boost: 2.0 | |
| 213 | - example: 'query=title:"芭比娃娃"' | |
| 214 | - language_field_mapping: | |
| 215 | - zh: | |
| 216 | - - "name" | |
| 217 | - en: | |
| 218 | - - "enSpuName" | |
| 219 | - ru: | |
| 220 | - - "ruSkuName" | |
| 221 | - | |
| 222 | - - name: "category" | |
| 223 | - label: "类目索引" | |
| 224 | - fields: | |
| 225 | - - "categoryName" | |
| 226 | - analyzer: "chinese_ecommerce" | |
| 227 | - boost: 1.5 | |
| 228 | - example: 'query=category:"玩具"' | |
| 229 | - | |
| 230 | - - name: "brand" | |
| 231 | - label: "品牌索引" | |
| 232 | - fields: | |
| 233 | - - "brandName" | |
| 234 | - analyzer: "chinese_ecommerce" | |
| 235 | - boost: 1.5 | |
| 236 | - example: 'query=brand:"ZHU LIN"' | |
| 237 | - | |
| 238 | -# Query Configuration | |
| 239 | -query_config: | |
| 240 | - supported_languages: | |
| 241 | - - "zh" | |
| 242 | - - "en" | |
| 243 | - - "ru" | |
| 244 | - default_language: "zh" | |
| 245 | - enable_translation: true | |
| 246 | - enable_text_embedding: true | |
| 247 | - enable_query_rewrite: true | |
| 248 | - | |
| 249 | - # Translation API (DeepL) | |
| 250 | - translation_service: "deepl" | |
| 251 | - translation_api_key: null # Set via environment variable | |
| 252 | - | |
| 253 | -# Ranking Configuration(已弃用,保留用于文档说明) | |
| 254 | -ranking: | |
| 255 | - expression: "bm25() + 0.2*text_embedding_relevance()" | |
| 256 | - description: "BM25 text relevance combined with semantic embedding similarity" | |
| 257 | - | |
| 258 | -# Function Score配置(ES层打分规则) | |
| 259 | -# 约定:function_score是查询结构的必需部分 | |
| 260 | -function_score: | |
| 261 | - score_mode: "sum" # multiply, sum, avg, first, max, min | |
| 262 | - boost_mode: "multiply" # multiply, replace, sum, avg, max, min | |
| 263 | - | |
| 264 | - functions: | |
| 265 | - # 1. Filter + Weight(条件权重)- 根据条件匹配提权 | |
| 266 | - - type: "filter_weight" | |
| 267 | - name: "7天新品提权" | |
| 268 | - filter: | |
| 269 | - range: | |
| 270 | - days_since_last_update: | |
| 271 | - lte: 7 | |
| 272 | - weight: 1.3 | |
| 273 | - | |
| 274 | - - type: "filter_weight" | |
| 275 | - name: "30天新品提权" | |
| 276 | - filter: | |
| 277 | - range: | |
| 278 | - days_since_last_update: | |
| 279 | - lte: 30 | |
| 280 | - weight: 1.15 | |
| 281 | - | |
| 282 | - - type: "filter_weight" | |
| 283 | - name: "有视频提权" | |
| 284 | - filter: | |
| 285 | - term: | |
| 286 | - is_video: true | |
| 287 | - weight: 1.05 | |
| 288 | - | |
| 289 | - # 示例:特定标签提权 | |
| 290 | - # - type: "filter_weight" | |
| 291 | - # name: "特定标签提权" | |
| 292 | - # filter: | |
| 293 | - # term: | |
| 294 | - # labelId_by_skuId_essa_3: 165 | |
| 295 | - # weight: 1.1 | |
| 296 | - | |
| 297 | - # 示例:主力价格段提权 | |
| 298 | - # - type: "filter_weight" | |
| 299 | - # name: "主力价格段" | |
| 300 | - # filter: | |
| 301 | - # range: | |
| 302 | - # price: | |
| 303 | - # gte: 50 | |
| 304 | - # lte: 200 | |
| 305 | - # weight: 1.1 | |
| 306 | - | |
| 307 | - # 2. Field Value Factor(字段值映射)- 将数值字段映射为打分因子 | |
| 308 | - # 示例:在售天数 | |
| 309 | - # - type: "field_value_factor" | |
| 310 | - # name: "在售天数因子" | |
| 311 | - # field: "on_sell_days_boost" | |
| 312 | - # factor: 1.0 | |
| 313 | - # modifier: "none" # none, log, log1p, log2p, ln, ln1p, ln2p, square, sqrt, reciprocal | |
| 314 | - # missing: 1.0 | |
| 315 | - | |
| 316 | - # 示例:销量因子 | |
| 317 | - # - type: "field_value_factor" | |
| 318 | - # name: "销量因子" | |
| 319 | - # field: "sales_count" | |
| 320 | - # factor: 0.01 | |
| 321 | - # modifier: "log1p" # 对数映射,避免极端值影响 | |
| 322 | - # missing: 1.0 | |
| 323 | - | |
| 324 | - # 示例:评分因子 | |
| 325 | - # - type: "field_value_factor" | |
| 326 | - # name: "评分因子" | |
| 327 | - # field: "rating" | |
| 328 | - # factor: 0.5 | |
| 329 | - # modifier: "sqrt" | |
| 330 | - # missing: 1.0 | |
| 331 | - | |
| 332 | - # 3. Decay Functions(衰减函数)- 距离原点越远分数越低 | |
| 333 | - # 示例:时间衰减 | |
| 334 | - # - type: "decay" | |
| 335 | - # name: "时间衰减" | |
| 336 | - # function: "gauss" # gauss, exp, linear | |
| 337 | - # field: "create_time" | |
| 338 | - # origin: "now" | |
| 339 | - # scale: "30d" | |
| 340 | - # offset: "0d" | |
| 341 | - # decay: 0.5 | |
| 342 | - | |
| 343 | - # 示例:价格衰减 | |
| 344 | - # - type: "decay" | |
| 345 | - # name: "价格衰减" | |
| 346 | - # function: "linear" | |
| 347 | - # field: "price" | |
| 348 | - # origin: "100" | |
| 349 | - # scale: "50" | |
| 350 | - # decay: 0.5 | |
| 351 | - | |
| 352 | -# Rerank配置(本地重排,当前禁用) | |
| 353 | -rerank: | |
| 354 | - enabled: false | |
| 355 | - expression: "" | |
| 356 | - description: "Local reranking (disabled, use ES function_score instead)" | |
| 357 | - | |
| 358 | -# SPU Aggregation (disabled for customer1) | |
| 359 | -spu_config: | |
| 360 | - enabled: false | |
| 361 | - spu_field: null | |
| 362 | - inner_hits_size: 3 | |
| 363 | - |
config/schema/customer1/query_rewrite.dict deleted
frontend/index.html
| ... | ... | @@ -121,9 +121,9 @@ |
| 121 | 121 | </div> |
| 122 | 122 | |
| 123 | 123 | <footer> |
| 124 | - <p>SearchEngine © 2025 | API: <span id="apiUrl">http://120.76.41.98:6002</span></p> | |
| 124 | + <p>SearchEngine © 2025 | API: <span id="apiUrl">Loading...</span></p> | |
| 125 | 125 | </footer> |
| 126 | 126 | |
| 127 | - <script src="/static/js/app.js?v=3.0"></script> | |
| 127 | + <script src="/static/js/app.js?v=3.1"></script> | |
| 128 | 128 | </body> |
| 129 | 129 | </html> | ... | ... |
frontend/static/js/app.js
| 1 | 1 | // SearchEngine Frontend - Modern UI (Multi-Tenant) |
| 2 | 2 | |
| 3 | -const API_BASE_URL = 'http://localhost:6002'; | |
| 4 | -document.getElementById('apiUrl').textContent = API_BASE_URL; | |
| 3 | +const API_BASE_URL = window.API_BASE_URL || 'http://localhost:6002'; | |
| 4 | +if (document.getElementById('apiUrl')) { | |
| 5 | + document.getElementById('apiUrl').textContent = API_BASE_URL; | |
| 6 | +} | |
| 5 | 7 | |
| 6 | 8 | // Get tenant ID from input |
| 7 | 9 | function getTenantId() { | ... | ... |
scripts/create_base_frontend.py deleted
| ... | ... | @@ -1,218 +0,0 @@ |
| 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 deleted
| ... | ... | @@ -1,198 +0,0 @@ |
| 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 api/app.py \ | |
| 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} (或 http://localhost:$FRONTEND_PORT/base.html)" | |
| 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
| ... | ... | @@ -10,6 +10,15 @@ import sys |
| 10 | 10 | import logging |
| 11 | 11 | import time |
| 12 | 12 | from collections import defaultdict, deque |
| 13 | +from pathlib import Path | |
| 14 | +from dotenv import load_dotenv | |
| 15 | + | |
| 16 | +# Load .env file | |
| 17 | +project_root = Path(__file__).parent.parent | |
| 18 | +load_dotenv(project_root / '.env') | |
| 19 | + | |
| 20 | +# Get API_BASE_URL from environment | |
| 21 | +API_BASE_URL = os.getenv('API_BASE_URL', 'http://localhost:6002') | |
| 13 | 22 | |
| 14 | 23 | # Change to frontend directory |
| 15 | 24 | frontend_dir = os.path.join(os.path.dirname(__file__), '../frontend') |
| ... | ... | @@ -46,19 +55,41 @@ class MyHTTPRequestHandler(http.server.SimpleHTTPRequestHandler, RateLimitingMix |
| 46 | 55 | """Custom request handler with CORS support and robust error handling.""" |
| 47 | 56 | |
| 48 | 57 | def do_GET(self): |
| 49 | - """Handle GET requests with support for base.html.""" | |
| 50 | - # Parse path (handle query strings) | |
| 51 | - path = self.path.split('?')[0] # Remove query string if present | |
| 58 | + """Handle GET requests with API config injection.""" | |
| 59 | + path = self.path.split('?')[0] | |
| 52 | 60 | |
| 53 | - # Route /base to base.html (handle both with and without trailing slash) | |
| 54 | - if path == '/base' or path == '/base/': | |
| 55 | - self.path = '/base.html' + (self.path.split('?', 1)[1] if '?' in self.path else '') | |
| 56 | - # Route / to index.html (default) | |
| 57 | - elif path == '/' or path == '': | |
| 61 | + # Route / to index.html | |
| 62 | + if path == '/' or path == '': | |
| 58 | 63 | self.path = '/index.html' + (self.path.split('?', 1)[1] if '?' in self.path else '') |
| 59 | 64 | |
| 60 | - # Call parent do_GET with modified path | |
| 61 | - super().do_GET() | |
| 65 | + # Inject API config for HTML files | |
| 66 | + if self.path.endswith('.html'): | |
| 67 | + self._serve_html_with_config() | |
| 68 | + else: | |
| 69 | + super().do_GET() | |
| 70 | + | |
| 71 | + def _serve_html_with_config(self): | |
| 72 | + """Serve HTML with API_BASE_URL injected.""" | |
| 73 | + try: | |
| 74 | + file_path = self.path.lstrip('/') | |
| 75 | + if not os.path.exists(file_path): | |
| 76 | + self.send_error(404) | |
| 77 | + return | |
| 78 | + | |
| 79 | + with open(file_path, 'r', encoding='utf-8') as f: | |
| 80 | + html = f.read() | |
| 81 | + | |
| 82 | + # Inject API_BASE_URL before app.js | |
| 83 | + config_script = f'<script>window.API_BASE_URL="{API_BASE_URL}";</script>\n ' | |
| 84 | + html = html.replace('<script src="/static/js/app.js', config_script + '<script src="/static/js/app.js', 1) | |
| 85 | + | |
| 86 | + self.send_response(200) | |
| 87 | + self.send_header('Content-Type', 'text/html; charset=utf-8') | |
| 88 | + self.end_headers() | |
| 89 | + self.wfile.write(html.encode('utf-8')) | |
| 90 | + except Exception as e: | |
| 91 | + logging.error(f"Error serving HTML: {e}") | |
| 92 | + self.send_error(500) | |
| 62 | 93 | |
| 63 | 94 | def setup(self): |
| 64 | 95 | """Setup with error handling.""" | ... | ... |
scripts/stop_base.sh deleted
| ... | ... | @@ -1,45 +0,0 @@ |
| 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 | - |