From fb68a0efdfba85b011ef38ac4e26a56c919b97e0 Mon Sep 17 00:00:00 2001 From: tangwang Date: Thu, 13 Nov 2025 12:34:24 +0800 Subject: [PATCH] 配置优化 --- .env | 8 ++++---- .env.example | 17 ++++++++++++----- config/env_config.py | 10 +++++----- frontend/static/js/app_base.js | 576 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ scripts/demo_base.sh | 2 +- scripts/generate_test_data.py | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++------------------------ scripts/import_test_data.py | 36 +++++++++++++++++++++++++++++++++--- scripts/ingest_shoplazza.py | 21 +++++++++++++++++---- test_data_base.sql | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ utils/db_connector.py | 5 +++-- 10 files changed, 770 insertions(+), 48 deletions(-) create mode 100644 frontend/static/js/app_base.js create mode 100644 test_data_base.sql diff --git a/.env b/.env index 49d9691..f109c3f 100644 --- a/.env +++ b/.env @@ -1,15 +1,15 @@ # Elasticsearch Configuration ES_HOST=http://localhost:9200 -ES_USERNAME= -ES_PASSWORD= +ES_USERNAME=essa +ES_PASSWORD=4hOaLaf41y2VuI8y # Redis Configuration (Optional) REDIS_HOST=localhost REDIS_PORT=6479 -REDIS_PASSWORD= +REDIS_PASSWORD=BMfv5aI31kgHWtlx # DeepL Translation API -DEEPL_AUTH_KEY= +DEEPL_AUTH_KEY=c9293ab4-ad25-479b-919f-ab4e63b429ed # Customer Configuration CUSTOMER_ID=customer1 diff --git a/.env.example b/.env.example index 6f45d6a..89e9239 100644 --- a/.env.example +++ b/.env.example @@ -1,18 +1,18 @@ -# Environment Configuration +# Environment Configuration Template # Copy this file to .env and update with your actual values # Elasticsearch Configuration (v8.18) ES_HOST=http://localhost:9200 -ES_USERNAME=essa -ES_PASSWORD=4hOaLaf41y2VuI8y +ES_USERNAME= +ES_PASSWORD= # Redis Configuration (for caching) REDIS_HOST=localhost REDIS_PORT=6479 -REDIS_PASSWORD=BMfv5aI31kgHWtlx +REDIS_PASSWORD= # DeepL Translation API -DEEPL_AUTH_KEY=c9293ab4-ad25-479b-919f-ab4e63b429ed +DEEPL_AUTH_KEY= # Customer Configuration CUSTOMER_ID=customer1 @@ -27,3 +27,10 @@ IMAGE_MODEL_DIR=/data/tw/models/cn-clip # Cache Directory CACHE_DIR=.cache + +# MySQL Database Configuration (Shoplazza) +DB_HOST= +DB_PORT=3306 +DB_DATABASE= +DB_USERNAME= +DB_PASSWORD= diff --git a/config/env_config.py b/config/env_config.py index ec22898..52fdfc8 100644 --- a/config/env_config.py +++ b/config/env_config.py @@ -47,11 +47,11 @@ CACHE_DIR = os.getenv('CACHE_DIR', '.cache') # MySQL Database Configuration (Shoplazza) DB_CONFIG = { - 'host': os.getenv('DB_HOST', '120.79.247.228'), - 'port': int(os.getenv('DB_PORT', 3316)), - 'database': os.getenv('DB_DATABASE', 'saas'), - 'username': os.getenv('DB_USERNAME', 'saas'), - 'password': os.getenv('DB_PASSWORD', 'P89cZHS5d7dFyc9R'), + 'host': os.getenv('DB_HOST'), + 'port': int(os.getenv('DB_PORT', 3306)) if os.getenv('DB_PORT') else 3306, + 'database': os.getenv('DB_DATABASE'), + 'username': os.getenv('DB_USERNAME'), + 'password': os.getenv('DB_PASSWORD'), } diff --git a/frontend/static/js/app_base.js b/frontend/static/js/app_base.js new file mode 100644 index 0000000..22aadb0 --- /dev/null +++ b/frontend/static/js/app_base.js @@ -0,0 +1,576 @@ +// SearchEngine Frontend - Modern UI + +const TENANT_ID = '1'; +const API_BASE_URL = 'http://localhost:6002'; +document.getElementById('apiUrl').textContent = API_BASE_URL; + +// State Management +let state = { + query: '', + currentPage: 1, + pageSize: 20, + totalResults: 0, + filters: {}, + rangeFilters: {}, + sortBy: '', + sortOrder: 'desc', + facets: null, + lastSearchData: null, + debug: true // Always enable debug mode for test frontend +}; + +// Initialize +document.addEventListener('DOMContentLoaded', function() { + console.log('SearchEngine loaded'); + console.log('Debug mode: always enabled (test frontend)'); + + document.getElementById('searchInput').focus(); +}); + +// Keyboard handler +function handleKeyPress(event) { + if (event.key === 'Enter') { + performSearch(); + } +} + +// Toggle filters visibility +function toggleFilters() { + const filterSection = document.getElementById('filterSection'); + filterSection.classList.toggle('hidden'); +} + +// Perform search +async function performSearch(page = 1) { + const query = document.getElementById('searchInput').value.trim(); + + if (!query) { + alert('Please enter search keywords'); + return; + } + + state.query = query; + state.currentPage = page; + state.pageSize = parseInt(document.getElementById('resultSize').value); + + const from = (page - 1) * state.pageSize; + + // Define facets (简化配置) + const facets = [ + { + "field": "category_keyword", + "size": 15, + "type": "terms" + }, + { + "field": "vendor_keyword", + "size": 15, + "type": "terms" + }, + { + "field": "tags_keyword", + "size": 10, + "type": "terms" + }, + { + "field": "min_price", + "type": "range", + "ranges": [ + {"key": "0-50", "to": 50}, + {"key": "50-100", "from": 50, "to": 100}, + {"key": "100-200", "from": 100, "to": 200}, + {"key": "200+", "from": 200} + ] + } + ]; + + // Show loading + document.getElementById('loading').style.display = 'block'; + document.getElementById('productGrid').innerHTML = ''; + + try { + const response = await fetch(`${API_BASE_URL}/search/`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Tenant-ID': TENANT_ID, + }, + body: JSON.stringify({ + query: query, + size: state.pageSize, + from: from, + filters: Object.keys(state.filters).length > 0 ? state.filters : null, + range_filters: Object.keys(state.rangeFilters).length > 0 ? state.rangeFilters : null, + facets: facets, + sort_by: state.sortBy || null, + sort_order: state.sortOrder, + debug: state.debug + }) + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const data = await response.json(); + state.lastSearchData = data; + state.totalResults = data.total; + state.facets = data.facets; + + displayResults(data); + displayFacets(data.facets); + displayPagination(); + displayDebugInfo(data); + updateProductCount(data.total); + updateClearFiltersButton(); + + } catch (error) { + console.error('Search error:', error); + document.getElementById('productGrid').innerHTML = ` +
+ Search Error: ${error.message} +

+ Please ensure backend service is running (${API_BASE_URL}) +
+ `; + } finally { + document.getElementById('loading').style.display = 'none'; + } +} + +// Display results in grid +function displayResults(data) { + const grid = document.getElementById('productGrid'); + + if (!data.results || data.results.length === 0) { + grid.innerHTML = ` +
+

No Results Found

+

Try different keywords or filters

+
+ `; + return; + } + + let html = ''; + + data.results.forEach((hit) => { + const score = product.relevance_score; + html += ` +
+
+ ${product.image_url ? ` + ${escapeHtml(product.title)} + ` : ` +
No Image
+ `} +
+ +
+ ${product.price ? `$${product.price.toFixed(2)}` : 'N/A'}${product.compare_at_price && product.compare_at_price > product.price ? `$${product.compare_at_price.toFixed(2)}` : \'\'} +
+ +
+ ${product.in_stock ? 'In Stock' : 'Out of Stock'} + ${product.variants && product.variants.length > 0 ? `(${product.variants.length} variants)` : ''} +
+ +
+ ${escapeHtml(product.title || product.title || 'N/A')} +
+ +
${product.vendor ? escapeHtml(product.vendor) : ''}${product.product_type ? ' | ' + escapeHtml(product.product_type) : ''}${product.category ? ' | ' + escapeHtml(product.category) : ''} ${product.tags ? ` +
+ Tags: ${escapeHtml(product.tags)} +
+ ` : ''} +
+ `; + }); + + grid.innerHTML = html; +} + +// Display facets as filter tags (重构版 - 标准化格式) +function displayFacets(facets) { + if (!facets) return; + + facets.forEach(facet => { + // 根据字段名找到对应的容器 + let containerId = null; + let maxDisplay = 10; + + if (facet.field === 'category_keyword') { + containerId = 'categoryTags'; + maxDisplay = 10; + } else if (facet.field === 'vendor_keyword') { + containerId = 'brandTags'; + maxDisplay = 10; + } else if (facet.field === 'tags_keyword') { + containerId = 'supplierTags'; + maxDisplay = 8; + } + + if (!containerId) return; + + const container = document.getElementById(containerId); + if (!container) return; + + let html = ''; + + // 渲染分面值 + facet.values.slice(0, maxDisplay).forEach(facetValue => { + const value = facetValue.value; + const count = facetValue.count; + const selected = facetValue.selected; + + html += ` + + ${escapeHtml(value)} (${count}) + + `; + }); + + container.innerHTML = html; + }); +} + +// Toggle filter +function toggleFilter(field, value) { + if (!state.filters[field]) { + state.filters[field] = []; + } + + const index = state.filters[field].indexOf(value); + if (index > -1) { + state.filters[field].splice(index, 1); + if (state.filters[field].length === 0) { + delete state.filters[field]; + } + } else { + state.filters[field].push(value); + } + + performSearch(1); // Reset to page 1 +} + +// Handle price filter (重构版 - 使用 rangeFilters) +function handlePriceFilter(value) { + if (!value) { + delete state.rangeFilters.price; + } else { + const priceRanges = { + '0-50': { lt: 50 }, + '50-100': { gte: 50, lt: 100 }, + '100-200': { gte: 100, lt: 200 }, + '200+': { gte: 200 } + }; + + if (priceRanges[value]) { + state.rangeFilters.price = priceRanges[value]; + } + } + + performSearch(1); +} + +// Handle time filter (重构版 - 使用 rangeFilters) +function handleTimeFilter(value) { + if (!value) { + delete state.rangeFilters.create_time; + } else { + const now = new Date(); + let fromDate; + + switch(value) { + case 'today': + fromDate = new Date(now.setHours(0, 0, 0, 0)); + break; + case 'week': + fromDate = new Date(now.setDate(now.getDate() - 7)); + break; + case 'month': + fromDate = new Date(now.setMonth(now.getMonth() - 1)); + break; + case '3months': + fromDate = new Date(now.setMonth(now.getMonth() - 3)); + break; + case '6months': + fromDate = new Date(now.setMonth(now.getMonth() - 6)); + break; + } + + if (fromDate) { + state.rangeFilters.create_time = { + gte: fromDate.toISOString() + }; + } + } + + performSearch(1); +} + +// Clear all filters +function clearAllFilters() { + state.filters = {}; + state.rangeFilters = {}; + document.getElementById('priceFilter').value = ''; + document.getElementById('timeFilter').value = ''; + performSearch(1); +} + +// Update clear filters button visibility +function updateClearFiltersButton() { + const btn = document.getElementById('clearFiltersBtn'); + if (Object.keys(state.filters).length > 0 || Object.keys(state.rangeFilters).length > 0) { + btn.style.display = 'inline-block'; + } else { + btn.style.display = 'none'; + } +} + +// Update product count +function updateProductCount(total) { + document.getElementById('productCount').textContent = `${total.toLocaleString()} products found`; +} + +// Sort functions +function setSortByDefault() { + // Remove active from all buttons and arrows + document.querySelectorAll('.sort-btn').forEach(b => b.classList.remove('active')); + document.querySelectorAll('.arrow-up, .arrow-down').forEach(a => a.classList.remove('active')); + + // Set default button active + const defaultBtn = document.querySelector('.sort-btn[data-sort=""]'); + if (defaultBtn) defaultBtn.classList.add('active'); + + state.sortBy = ''; + state.sortOrder = 'desc'; + + performSearch(1); +} + +function sortByField(field, order) { + state.sortBy = field; + state.sortOrder = order; + + // Remove active from all buttons (but keep "By default" if no sort) + document.querySelectorAll('.sort-btn').forEach(b => b.classList.remove('active')); + + // Remove active from all arrows + document.querySelectorAll('.arrow-up, .arrow-down').forEach(a => a.classList.remove('active')); + + // Add active to clicked arrow + const activeArrow = document.querySelector(`.arrow-up[data-field="${field}"][data-order="${order}"], .arrow-down[data-field="${field}"][data-order="${order}"]`); + if (activeArrow) { + activeArrow.classList.add('active'); + } + + performSearch(state.currentPage); +} + +// Pagination +function displayPagination() { + const paginationDiv = document.getElementById('pagination'); + + if (state.totalResults <= state.pageSize) { + paginationDiv.style.display = 'none'; + return; + } + + paginationDiv.style.display = 'flex'; + + const totalPages = Math.ceil(state.totalResults / state.pageSize); + const currentPage = state.currentPage; + + let html = ` + + `; + + // Page numbers + const maxVisible = 5; + let startPage = Math.max(1, currentPage - Math.floor(maxVisible / 2)); + let endPage = Math.min(totalPages, startPage + maxVisible - 1); + + if (endPage - startPage < maxVisible - 1) { + startPage = Math.max(1, endPage - maxVisible + 1); + } + + if (startPage > 1) { + html += ``; + if (startPage > 2) { + html += `...`; + } + } + + for (let i = startPage; i <= endPage; i++) { + html += ` + + `; + } + + if (endPage < totalPages) { + if (endPage < totalPages - 1) { + html += `...`; + } + html += ``; + } + + html += ` + + `; + + html += ` + + Page ${currentPage} of ${totalPages} (${state.totalResults.toLocaleString()} results) + + `; + + paginationDiv.innerHTML = html; +} + +function goToPage(page) { + const totalPages = Math.ceil(state.totalResults / state.pageSize); + if (page < 1 || page > totalPages) return; + + performSearch(page); + + // Scroll to top + window.scrollTo({ top: 0, behavior: 'smooth' }); +} + +// Display debug info +function displayDebugInfo(data) { + const debugInfoDiv = document.getElementById('debugInfo'); + + if (!state.debug || !data.debug_info) { + // If debug mode is off or no debug info, show basic query info + if (data.query_info) { + let html = '
'; + html += `
查询: ${escapeHtml(data.query_info.original_query || 'N/A')}
`; + html += `
语言: ${getLanguageName(data.query_info.detected_language)}
`; + html += '
'; + debugInfoDiv.innerHTML = html; + } else { + debugInfoDiv.innerHTML = ''; + } + return; + } + + // Display comprehensive debug info when debug mode is on + const debugInfo = data.debug_info; + let html = '
'; + + // Query Analysis + if (debugInfo.query_analysis) { + html += '
查询分析:'; + html += `
原始查询: ${escapeHtml(debugInfo.query_analysis.original_query || 'N/A')}
`; + html += `
标准化: ${escapeHtml(debugInfo.query_analysis.normalized_query || 'N/A')}
`; + html += `
重写后: ${escapeHtml(debugInfo.query_analysis.rewritten_query || 'N/A')}
`; + html += `
检测语言: ${getLanguageName(debugInfo.query_analysis.detected_language)}
`; + html += `
域: ${escapeHtml(debugInfo.query_analysis.domain || 'default')}
`; + html += `
简单查询: ${debugInfo.query_analysis.is_simple_query ? '是' : '否'}
`; + + if (debugInfo.query_analysis.translations && Object.keys(debugInfo.query_analysis.translations).length > 0) { + html += '
翻译: '; + for (const [lang, translation] of Object.entries(debugInfo.query_analysis.translations)) { + if (translation) { + html += `${getLanguageName(lang)}: ${escapeHtml(translation)}; `; + } + } + html += '
'; + } + + if (debugInfo.query_analysis.boolean_ast) { + html += `
布尔AST: ${escapeHtml(debugInfo.query_analysis.boolean_ast)}
`; + } + + html += `
向量嵌入: ${debugInfo.query_analysis.has_vector ? '已启用' : '未启用'}
`; + html += '
'; + } + + // Feature Flags + if (debugInfo.feature_flags) { + html += '
功能开关:'; + html += `
翻译: ${debugInfo.feature_flags.translation_enabled ? '启用' : '禁用'}
`; + html += `
嵌入: ${debugInfo.feature_flags.embedding_enabled ? '启用' : '禁用'}
`; + html += `
重排序: ${debugInfo.feature_flags.rerank_enabled ? '启用' : '禁用'}
`; + html += '
'; + } + + // ES Response + if (debugInfo.es_response) { + html += '
ES响应:'; + html += `
耗时: ${debugInfo.es_response.took_ms}ms
`; + html += `
总命中数: ${debugInfo.es_response.total_hits}
`; + html += `
最高分: ${debugInfo.es_response.max_score?.toFixed(3) || 0}
`; + html += '
'; + } + + // Stage Timings + if (debugInfo.stage_timings) { + html += '
各阶段耗时:'; + for (const [stage, duration] of Object.entries(debugInfo.stage_timings)) { + html += `
${stage}: ${duration.toFixed(2)}ms
`; + } + html += '
'; + } + + // ES Query + if (debugInfo.es_query) { + html += '
ES查询DSL:'; + html += `
${escapeHtml(JSON.stringify(debugInfo.es_query, null, 2))}
`; + html += '
'; + } + + html += '
'; + debugInfoDiv.innerHTML = html; +} + +// Helper functions +function escapeHtml(text) { + if (!text) return ''; + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; +} + +function escapeAttr(text) { + if (!text) return ''; + return text.replace(/'/g, "\\'").replace(/"/g, '"'); +} + +function formatDate(dateStr) { + if (!dateStr) return ''; + try { + const date = new Date(dateStr); + return date.toLocaleDateString('zh-CN'); + } catch { + return dateStr; + } +} + +function getLanguageName(code) { + const names = { + 'zh': '中文', + 'en': 'English', + 'ru': 'Русский', + 'ar': 'العربية', + 'ja': '日本語', + 'unknown': 'Unknown' + }; + return names[code] || code; +} diff --git a/scripts/demo_base.sh b/scripts/demo_base.sh index 2ee1f84..6d266d1 100755 --- a/scripts/demo_base.sh +++ b/scripts/demo_base.sh @@ -100,7 +100,7 @@ echo "后端服务将在后台运行..." mkdir -p logs # 启动后端(后台运行) -nohup python -m api.app \ +nohup python api/app.py \ --host 0.0.0.0 \ --port "$API_PORT" \ --customer base \ diff --git a/scripts/generate_test_data.py b/scripts/generate_test_data.py index 5eba021..5f86e81 100644 --- a/scripts/generate_test_data.py +++ b/scripts/generate_test_data.py @@ -200,7 +200,7 @@ def generate_sku_data(spus: list, start_sku_id: int = 1): 'image_src': '', 'wholesale_price': '[{"price": ' + str(round(price * 0.8, 2)) + ', "minQuantity": 10}]', 'note': '', - 'extend': '', + 'extend': None, # JSON field, use NULL instead of empty string 'shoplazza_created_at': created_at.strftime('%Y-%m-%d %H:%M:%S'), 'shoplazza_updated_at': updated_at.strftime('%Y-%m-%d %H:%M:%S'), 'tenant_id': spu['tenant_id'], @@ -216,6 +216,21 @@ def generate_sku_data(spus: list, start_sku_id: int = 1): return skus +def escape_sql_string(value: str) -> str: + """ + Escape SQL string value (replace single quotes with doubled quotes). + + Args: + value: String value to escape + + Returns: + Escaped string + """ + if value is None: + return '' + return str(value).replace("'", "''").replace("\\", "\\\\") + + def generate_sql_inserts(spus: list, skus: list, output_file: str): """ Generate SQL INSERT statements. @@ -241,21 +256,26 @@ def generate_sql_inserts(spus: list, skus: list, output_file: str): for i, spu in enumerate(spus): values = ( - f"({spu['id']}, {spu['shop_id']}, '{spu['shoplazza_id']}', " - f"'{spu['handle']}', '{spu['title']}', '{spu['brief']}', " - f"'{spu['description']}', '{spu['spu']}', '{spu['vendor']}', " - f"'{spu['vendor_url']}', '{spu['seo_title']}', '{spu['seo_description']}', " - f"'{spu['seo_keywords']}', '{spu['image_src']}', {spu['image_width']}, " - f"{spu['image_height']}, '{spu['image_path']}', '{spu['image_alt']}', " - f"'{spu['inventory_policy']}', {spu['inventory_quantity']}, " - f"'{spu['inventory_tracking']}', {spu['published']}, " - f"'{spu['published_at']}', {spu['requires_shipping']}, {spu['taxable']}, " + f"({spu['id']}, {spu['shop_id']}, '{escape_sql_string(spu['shoplazza_id'])}', " + f"'{escape_sql_string(spu['handle'])}', '{escape_sql_string(spu['title'])}', " + f"'{escape_sql_string(spu['brief'])}', '{escape_sql_string(spu['description'])}', " + f"'{escape_sql_string(spu['spu'])}', '{escape_sql_string(spu['vendor'])}', " + f"'{escape_sql_string(spu['vendor_url'])}', '{escape_sql_string(spu['seo_title'])}', " + f"'{escape_sql_string(spu['seo_description'])}', '{escape_sql_string(spu['seo_keywords'])}', " + f"'{escape_sql_string(spu['image_src'])}', {spu['image_width']}, " + f"{spu['image_height']}, '{escape_sql_string(spu['image_path'])}', " + f"'{escape_sql_string(spu['image_alt'])}', '{escape_sql_string(spu['inventory_policy'])}', " + f"{spu['inventory_quantity']}, '{escape_sql_string(spu['inventory_tracking'])}', " + f"{spu['published']}, '{escape_sql_string(spu['published_at'])}', " + f"{spu['requires_shipping']}, {spu['taxable']}, " f"{spu['fake_sales']}, {spu['display_fake_sales']}, {spu['mixed_wholesale']}, " f"{spu['need_variant_image']}, {spu['has_only_default_variant']}, " - f"'{spu['tags']}', '{spu['note']}', '{spu['category']}', " - f"'{spu['shoplazza_created_at']}', '{spu['shoplazza_updated_at']}', " - f"'{spu['tenant_id']}', '{spu['creator']}', '{spu['create_time']}', " - f"'{spu['updater']}', '{spu['update_time']}', {spu['deleted']})" + f"'{escape_sql_string(spu['tags'])}', '{escape_sql_string(spu['note'])}', " + f"'{escape_sql_string(spu['category'])}', '{escape_sql_string(spu['shoplazza_created_at'])}', " + f"'{escape_sql_string(spu['shoplazza_updated_at'])}', '{escape_sql_string(spu['tenant_id'])}', " + f"'{escape_sql_string(spu['creator'])}', '{escape_sql_string(spu['create_time'])}', " + f"'{escape_sql_string(spu['updater'])}', '{escape_sql_string(spu['update_time'])}', " + f"{spu['deleted']})" ) f.write(values) if i < len(spus) - 1: @@ -274,18 +294,24 @@ def generate_sql_inserts(spus: list, skus: list, output_file: str): f.write(") VALUES\n") for i, sku in enumerate(skus): + # Handle extend field (JSON, can be NULL) + extend_value = 'NULL' if sku['extend'] is None else f"'{escape_sql_string(sku['extend'])}'" + values = ( - f"({sku['id']}, {sku['spu_id']}, {sku['shop_id']}, '{sku['shoplazza_id']}', " - f"'{sku['shoplazza_product_id']}', '{sku['shoplazza_image_id']}', " - f"'{sku['title']}', '{sku['sku']}', '{sku['barcode']}', {sku['position']}, " + f"({sku['id']}, {sku['spu_id']}, {sku['shop_id']}, '{escape_sql_string(sku['shoplazza_id'])}', " + f"'{escape_sql_string(sku['shoplazza_product_id'])}', '{escape_sql_string(sku['shoplazza_image_id'])}', " + f"'{escape_sql_string(sku['title'])}', '{escape_sql_string(sku['sku'])}', " + f"'{escape_sql_string(sku['barcode'])}', {sku['position']}, " f"{sku['price']}, {sku['compare_at_price']}, {sku['cost_price']}, " - f"'{sku['option1']}', '{sku['option2']}', '{sku['option3']}', " - f"{sku['inventory_quantity']}, {sku['weight']}, '{sku['weight_unit']}', " - f"'{sku['image_src']}', '{sku['wholesale_price']}', '{sku['note']}', " - f"'{sku['extend']}', '{sku['shoplazza_created_at']}', " - f"'{sku['shoplazza_updated_at']}', '{sku['tenant_id']}', " - f"'{sku['creator']}', '{sku['create_time']}', '{sku['updater']}', " - f"'{sku['update_time']}', {sku['deleted']})" + f"'{escape_sql_string(sku['option1'])}', '{escape_sql_string(sku['option2'])}', " + f"'{escape_sql_string(sku['option3'])}', {sku['inventory_quantity']}, {sku['weight']}, " + f"'{escape_sql_string(sku['weight_unit'])}', '{escape_sql_string(sku['image_src'])}', " + f"'{escape_sql_string(sku['wholesale_price'])}', '{escape_sql_string(sku['note'])}', " + f"{extend_value}, '{escape_sql_string(sku['shoplazza_created_at'])}', " + f"'{escape_sql_string(sku['shoplazza_updated_at'])}', '{escape_sql_string(sku['tenant_id'])}', " + f"'{escape_sql_string(sku['creator'])}', '{escape_sql_string(sku['create_time'])}', " + f"'{escape_sql_string(sku['updater'])}', '{escape_sql_string(sku['update_time'])}', " + f"{sku['deleted']})" ) f.write(values) if i < len(skus) - 1: diff --git a/scripts/import_test_data.py b/scripts/import_test_data.py index acf2cef..621bfc1 100644 --- a/scripts/import_test_data.py +++ b/scripts/import_test_data.py @@ -24,11 +24,25 @@ def import_sql_file(db_engine, sql_file: str): db_engine: SQLAlchemy database engine sql_file: Path to SQL file """ + from sqlalchemy import text + with open(sql_file, 'r', encoding='utf-8') as f: sql_content = f.read() # Split by semicolons to get individual statements - statements = [s.strip() for s in sql_content.split(';') if s.strip() and not s.strip().startswith('--')] + # Remove comments and empty lines, then split by semicolon + lines = [] + for line in sql_content.split('\n'): + # Remove inline comments + if '--' in line: + line = line[:line.index('--')] + line = line.strip() + if line and not line.startswith('--'): + lines.append(line) + + # Join lines and split by semicolon + full_text = ' '.join(lines) + statements = [s.strip() for s in full_text.split(';') if s.strip()] print(f"Executing {len(statements)} SQL statements...") @@ -36,12 +50,12 @@ def import_sql_file(db_engine, sql_file: str): for i, statement in enumerate(statements, 1): if statement: try: - conn.execute(statement) + conn.execute(text(statement)) conn.commit() print(f" [{i}/{len(statements)}] Executed successfully") except Exception as e: print(f" [{i}/{len(statements)}] ERROR: {e}") - print(f" Statement: {statement[:100]}...") + print(f" Statement: {statement[:200]}...") raise @@ -109,6 +123,22 @@ def main(): print("Database connection successful") + # Clean existing data if tenant_id provided + if args.tenant_id: + print(f"\nCleaning existing data for tenant_id: {args.tenant_id}") + from sqlalchemy import text + try: + with db_engine.connect() as conn: + # Delete SKUs first (foreign key constraint) + conn.execute(text(f"DELETE FROM shoplazza_product_sku WHERE tenant_id = '{args.tenant_id}'")) + # Delete SPUs + conn.execute(text(f"DELETE FROM shoplazza_product_spu WHERE tenant_id = '{args.tenant_id}'")) + conn.commit() + print("✓ Existing data cleaned") + except Exception as e: + print(f"⚠ Warning: Failed to clean existing data: {e}") + # Continue anyway + # Import SQL file print(f"\nImporting SQL file: {args.sql_file}") try: diff --git a/scripts/ingest_shoplazza.py b/scripts/ingest_shoplazza.py index a48ab6c..37b5e22 100644 --- a/scripts/ingest_shoplazza.py +++ b/scripts/ingest_shoplazza.py @@ -78,11 +78,24 @@ def main(): print(f"ERROR: Failed to connect to MySQL: {e}") return 1 - # Connect to Elasticsearch - print(f"Connecting to Elasticsearch: {args.es_host}") - es_client = ESClient(hosts=[args.es_host]) + # Connect to Elasticsearch (use unified config loading) + from config.env_config import get_es_config + es_config = get_es_config() + + # Use provided es_host or fallback to config + es_host = args.es_host or es_config.get('host', 'http://localhost:9200') + es_username = es_config.get('username') + es_password = es_config.get('password') + + print(f"Connecting to Elasticsearch: {es_host}") + if es_username and es_password: + print(f"Using authentication: {es_username}") + es_client = ESClient(hosts=[es_host], username=es_username, password=es_password) + else: + es_client = ESClient(hosts=[es_host]) + if not es_client.ping(): - print(f"ERROR: Cannot connect to Elasticsearch at {args.es_host}") + print(f"ERROR: Cannot connect to Elasticsearch at {es_host}") return 1 # Generate and create index diff --git a/test_data_base.sql b/test_data_base.sql new file mode 100644 index 0000000..c97a400 --- /dev/null +++ b/test_data_base.sql @@ -0,0 +1,69 @@ +-- SPU Test Data +INSERT INTO shoplazza_product_spu ( + id, shop_id, shoplazza_id, handle, title, brief, description, spu, + vendor, vendor_url, seo_title, seo_description, seo_keywords, + image_src, image_width, image_height, image_path, image_alt, + inventory_policy, inventory_quantity, inventory_tracking, + published, published_at, requires_shipping, taxable, + fake_sales, display_fake_sales, mixed_wholesale, need_variant_image, + has_only_default_variant, tags, note, category, + shoplazza_created_at, shoplazza_updated_at, tenant_id, + creator, create_time, updater, update_time, deleted +) VALUES +(1, 1, 'spu-1', 'product-1', '音响 海尔', '蓝牙无线音响', '

蓝牙无线音响,来自海尔品牌。Bluetooth wireless speaker

', '', '海尔', 'https://海尔.com', '音响 海尔 - 服装', '购买海尔音响,蓝牙无线音响', '音响,海尔,服装', '//cdn.example.com/products/1.jpg', 800, 600, 'products/1.jpg', '音响 海尔', '', 0, '0', 1, '2025-10-29 12:17:09', 1, 0, 0, 0, 0, 0, 0, '服装,海尔,音响', '', '服装', '2025-10-29 12:17:09', '2025-11-05 12:17:09', '1', '1', '2025-10-29 12:17:09', '1', '2025-11-05 12:17:09', 0), +(2, 1, 'spu-2', 'product-2', '无线鼠标 华为', '人体工学无线鼠标', '

人体工学无线鼠标,来自华为品牌。Ergonomic wireless mouse

', '', '华为', 'https://华为.com', '无线鼠标 华为 - 服装', '购买华为无线鼠标,人体工学无线鼠标', '无线鼠标,华为,服装', '//cdn.example.com/products/2.jpg', 800, 600, 'products/2.jpg', '无线鼠标 华为', '', 0, '0', 1, '2025-09-30 12:17:09', 1, 0, 0, 0, 0, 0, 0, '服装,华为,无线鼠标', '', '服装', '2025-09-30 12:17:09', '2025-10-01 12:17:09', '1', '1', '2025-09-30 12:17:09', '1', '2025-10-01 12:17:09', 0), +(3, 1, 'spu-3', 'product-3', '显示器 Sony', '4K高清显示器', '

4K高清显示器,来自Sony品牌。4K high-definition monitor

', '', 'Sony', 'https://sony.com', '显示器 Sony - 服装', '购买Sony显示器,4K高清显示器', '显示器,Sony,服装', '//cdn.example.com/products/3.jpg', 800, 600, 'products/3.jpg', '显示器 Sony', '', 0, '0', 1, '2025-03-30 12:17:09', 1, 0, 0, 0, 0, 0, 0, '服装,Sony,显示器', '', '服装', '2025-03-30 12:17:09', '2025-04-05 12:17:09', '1', '1', '2025-03-30 12:17:09', '1', '2025-04-05 12:17:09', 0), +(4, 1, 'spu-4', 'product-4', '蓝牙耳机 Apple', '高品质无线蓝牙耳机', '

高品质无线蓝牙耳机,来自Apple品牌。High-quality wireless Bluetooth headphone

', '', 'Apple', 'https://apple.com', '蓝牙耳机 Apple - 电子产品', '购买Apple蓝牙耳机,高品质无线蓝牙耳机', '蓝牙耳机,Apple,电子产品', '//cdn.example.com/products/4.jpg', 800, 600, 'products/4.jpg', '蓝牙耳机 Apple', '', 0, '0', 1, '2025-05-28 12:17:09', 1, 0, 0, 0, 0, 0, 0, '电子产品,Apple,蓝牙耳机', '', '电子产品', '2025-05-28 12:17:09', '2025-06-08 12:17:09', '1', '1', '2025-05-28 12:17:09', '1', '2025-06-08 12:17:09', 0), +(5, 1, 'spu-5', 'product-5', '智能手表 海尔', '多功能智能手表', '

多功能智能手表,来自海尔品牌。Multi-function smart watch

', '', '海尔', 'https://海尔.com', '智能手表 海尔 - 图书', '购买海尔智能手表,多功能智能手表', '智能手表,海尔,图书', '//cdn.example.com/products/5.jpg', 800, 600, 'products/5.jpg', '智能手表 海尔', '', 0, '0', 1, '2025-08-13 12:17:09', 1, 0, 0, 0, 0, 0, 0, '图书,海尔,智能手表', '', '图书', '2025-08-13 12:17:09', '2025-08-26 12:17:09', '1', '1', '2025-08-13 12:17:09', '1', '2025-08-26 12:17:09', 0), +(6, 1, 'spu-6', 'product-6', '无线鼠标 Samsung', '人体工学无线鼠标', '

人体工学无线鼠标,来自Samsung品牌。Ergonomic wireless mouse

', '', 'Samsung', 'https://samsung.com', '无线鼠标 Samsung - 服装', '购买Samsung无线鼠标,人体工学无线鼠标', '无线鼠标,Samsung,服装', '//cdn.example.com/products/6.jpg', 800, 600, 'products/6.jpg', '无线鼠标 Samsung', '', 0, '0', 1, '2025-05-28 12:17:09', 1, 0, 0, 0, 0, 0, 0, '服装,Samsung,无线鼠标', '', '服装', '2025-05-28 12:17:09', '2025-06-03 12:17:09', '1', '1', '2025-05-28 12:17:09', '1', '2025-06-03 12:17:09', 0), +(7, 1, 'spu-7', 'product-7', '显示器 美的', '4K高清显示器', '

4K高清显示器,来自美的品牌。4K high-definition monitor

', '', '美的', 'https://美的.com', '显示器 美的 - 电子产品', '购买美的显示器,4K高清显示器', '显示器,美的,电子产品', '//cdn.example.com/products/7.jpg', 800, 600, 'products/7.jpg', '显示器 美的', '', 0, '0', 1, '2025-02-03 12:17:09', 1, 0, 0, 0, 0, 0, 0, '电子产品,美的,显示器', '', '电子产品', '2025-02-03 12:17:09', '2025-02-28 12:17:09', '1', '1', '2025-02-03 12:17:09', '1', '2025-02-28 12:17:09', 0), +(8, 1, 'spu-8', 'product-8', '智能手机 华为', '高性能智能手机', '

高性能智能手机,来自华为品牌。High-performance smartphone

', '', '华为', 'https://华为.com', '智能手机 华为 - 运动用品', '购买华为智能手机,高性能智能手机', '智能手机,华为,运动用品', '//cdn.example.com/products/8.jpg', 800, 600, 'products/8.jpg', '智能手机 华为', '', 0, '0', 1, '2025-08-21 12:17:09', 1, 0, 0, 0, 0, 0, 0, '运动用品,华为,智能手机', '', '运动用品', '2025-08-21 12:17:09', '2025-09-02 12:17:09', '1', '1', '2025-08-21 12:17:09', '1', '2025-09-02 12:17:09', 0), +(9, 1, 'spu-9', 'product-9', '运动鞋 Apple', '舒适透气的运动鞋', '

舒适透气的运动鞋,来自Apple品牌。Comfortable and breathable running shoes

', '', 'Apple', 'https://apple.com', '运动鞋 Apple - 图书', '购买Apple运动鞋,舒适透气的运动鞋', '运动鞋,Apple,图书', '//cdn.example.com/products/9.jpg', 800, 600, 'products/9.jpg', '运动鞋 Apple', '', 0, '0', 1, '2025-07-22 12:17:09', 1, 0, 0, 0, 0, 0, 0, '图书,Apple,运动鞋', '', '图书', '2025-07-22 12:17:09', '2025-08-03 12:17:09', '1', '1', '2025-07-22 12:17:09', '1', '2025-08-03 12:17:09', 0), +(10, 1, 'spu-10', 'product-10', '机械键盘 Sony', 'RGB背光机械键盘', '

RGB背光机械键盘,来自Sony品牌。RGB backlit mechanical keyboard

', '', 'Sony', 'https://sony.com', '机械键盘 Sony - 电子产品', '购买Sony机械键盘,RGB背光机械键盘', '机械键盘,Sony,电子产品', '//cdn.example.com/products/10.jpg', 800, 600, 'products/10.jpg', '机械键盘 Sony', '', 0, '0', 1, '2025-02-24 12:17:09', 1, 0, 0, 0, 0, 0, 0, '电子产品,Sony,机械键盘', '', '电子产品', '2025-02-24 12:17:09', '2025-03-25 12:17:09', '1', '1', '2025-02-24 12:17:09', '1', '2025-03-25 12:17:09', 0); + +-- SKU Test Data +INSERT INTO shoplazza_product_sku ( + id, spu_id, shop_id, shoplazza_id, shoplazza_product_id, shoplazza_image_id, + title, sku, barcode, position, price, compare_at_price, cost_price, + option1, option2, option3, inventory_quantity, weight, weight_unit, + image_src, wholesale_price, note, extend, + shoplazza_created_at, shoplazza_updated_at, tenant_id, + creator, create_time, updater, update_time, deleted +) VALUES +(1, 1, 1, 'sku-1', 'spu-1', '', '灰色 / S', 'SKU-1-1', 'BAR00000001', 1, 256.65, 315.84, 153.99, '灰色', 'S', '', 83, 2.19, 'kg', '', '[{"price": 205.32, "minQuantity": 10}]', '', NULL, '2025-02-01 12:17:09', '2025-02-26 12:17:09', '1', '1', '2025-02-01 12:17:09', '1', '2025-02-26 12:17:09', 0), +(2, 1, 1, 'sku-2', 'spu-1', '', '绿色 / XXL', 'SKU-1-2', 'BAR00000002', 2, 274.8, 345.42, 164.88, '绿色', 'XXL', '', 81, 2.73, 'kg', '', '[{"price": 219.84, "minQuantity": 10}]', '', NULL, '2025-09-04 12:17:09', '2025-09-18 12:17:09', '1', '1', '2025-09-04 12:17:09', '1', '2025-09-18 12:17:09', 0), +(3, 1, 1, 'sku-3', 'spu-1', '', '黑色 / XL', 'SKU-1-3', 'BAR00000003', 3, 245.37, 320.02, 147.22, '黑色', 'XL', '', 53, 0.28, 'kg', '', '[{"price": 196.3, "minQuantity": 10}]', '', NULL, '2025-07-20 12:17:09', '2025-07-23 12:17:09', '1', '1', '2025-07-20 12:17:09', '1', '2025-07-23 12:17:09', 0), +(4, 1, 1, 'sku-4', 'spu-1', '', '红色 / M', 'SKU-1-4', 'BAR00000004', 4, 238.24, 332.91, 142.94, '红色', 'M', '', 71, 3.23, 'kg', '', '[{"price": 190.59, "minQuantity": 10}]', '', NULL, '2025-06-30 12:17:09', '2025-07-01 12:17:09', '1', '1', '2025-06-30 12:17:09', '1', '2025-07-01 12:17:09', 0), +(5, 2, 1, 'sku-5', 'spu-2', '', '黑色 / L', 'SKU-2-1', 'BAR00000005', 1, 449.1, 659.64, 269.46, '黑色', 'L', '', 88, 1.54, 'kg', '', '[{"price": 359.28, "minQuantity": 10}]', '', NULL, '2025-07-30 12:17:09', '2025-08-04 12:17:09', '1', '1', '2025-07-30 12:17:09', '1', '2025-08-04 12:17:09', 0), +(6, 2, 1, 'sku-6', 'spu-2', '', '绿色 / M', 'SKU-2-2', 'BAR00000006', 2, 385.8, 510.27, 231.48, '绿色', 'M', '', 90, 2.78, 'kg', '', '[{"price": 308.64, "minQuantity": 10}]', '', NULL, '2024-12-21 12:17:09', '2024-12-23 12:17:09', '1', '1', '2024-12-21 12:17:09', '1', '2024-12-23 12:17:09', 0), +(7, 2, 1, 'sku-7', 'spu-2', '', '白色 / XXL', 'SKU-2-3', 'BAR00000007', 3, 444.82, 652.28, 266.89, '白色', 'XXL', '', 4, 1.1, 'kg', '', '[{"price": 355.86, "minQuantity": 10}]', '', NULL, '2025-07-23 12:17:09', '2025-07-25 12:17:09', '1', '1', '2025-07-23 12:17:09', '1', '2025-07-25 12:17:09', 0), +(8, 2, 1, 'sku-8', 'spu-2', '', '蓝色 / M', 'SKU-2-4', 'BAR00000008', 4, 412.17, 574.41, 247.3, '蓝色', 'M', '', 90, 4.34, 'kg', '', '[{"price": 329.73, "minQuantity": 10}]', '', NULL, '2025-04-01 12:17:09', '2025-04-15 12:17:09', '1', '1', '2025-04-01 12:17:09', '1', '2025-04-15 12:17:09', 0), +(9, 3, 1, 'sku-9', 'spu-3', '', '白色 / S', 'SKU-3-1', 'BAR00000009', 1, 424.04, 542.91, 254.42, '白色', 'S', '', 75, 1.6, 'kg', '', '[{"price": 339.23, "minQuantity": 10}]', '', NULL, '2025-07-08 12:17:09', '2025-07-24 12:17:09', '1', '1', '2025-07-08 12:17:09', '1', '2025-07-24 12:17:09', 0), +(10, 3, 1, 'sku-10', 'spu-3', '', '蓝色 / XXL', 'SKU-3-2', 'BAR00000010', 2, 446.94, 555.31, 268.16, '蓝色', 'XXL', '', 36, 4.5, 'kg', '', '[{"price": 357.55, "minQuantity": 10}]', '', NULL, '2025-06-20 12:17:09', '2025-06-22 12:17:09', '1', '1', '2025-06-20 12:17:09', '1', '2025-06-22 12:17:09', 0), +(11, 3, 1, 'sku-11', 'spu-3', '', '灰色 / S', 'SKU-3-3', 'BAR00000011', 3, 423.72, 606.22, 254.23, '灰色', 'S', '', 77, 3.42, 'kg', '', '[{"price": 338.97, "minQuantity": 10}]', '', NULL, '2025-09-17 12:17:09', '2025-09-21 12:17:09', '1', '1', '2025-09-17 12:17:09', '1', '2025-09-21 12:17:09', 0), +(12, 3, 1, 'sku-12', 'spu-3', '', '灰色 / S', 'SKU-3-4', 'BAR00000012', 4, 416.76, 525.39, 250.06, '灰色', 'S', '', 79, 4.83, 'kg', '', '[{"price": 333.41, "minQuantity": 10}]', '', NULL, '2025-07-26 12:17:09', '2025-08-10 12:17:09', '1', '1', '2025-07-26 12:17:09', '1', '2025-08-10 12:17:09', 0), +(13, 4, 1, 'sku-13', 'spu-4', '', '灰色 / M', 'SKU-4-1', 'BAR00000013', 1, 452.46, 549.77, 271.48, '灰色', 'M', '', 16, 1.68, 'kg', '', '[{"price": 361.97, "minQuantity": 10}]', '', NULL, '2025-10-26 12:17:09', '2025-11-05 12:17:09', '1', '1', '2025-10-26 12:17:09', '1', '2025-11-05 12:17:09', 0), +(14, 4, 1, 'sku-14', 'spu-4', '', '绿色 / L', 'SKU-4-2', 'BAR00000014', 2, 425.48, 514.03, 255.29, '绿色', 'L', '', 24, 3.86, 'kg', '', '[{"price": 340.38, "minQuantity": 10}]', '', NULL, '2025-07-10 12:17:09', '2025-07-27 12:17:09', '1', '1', '2025-07-10 12:17:09', '1', '2025-07-27 12:17:09', 0), +(15, 4, 1, 'sku-15', 'spu-4', '', '黑色 / S', 'SKU-4-3', 'BAR00000015', 3, 454.51, 652.31, 272.71, '黑色', 'S', '', 50, 0.15, 'kg', '', '[{"price": 363.61, "minQuantity": 10}]', '', NULL, '2025-05-15 12:17:09', '2025-05-21 12:17:09', '1', '1', '2025-05-15 12:17:09', '1', '2025-05-21 12:17:09', 0), +(16, 4, 1, 'sku-16', 'spu-4', '', '蓝色 / S', 'SKU-4-4', 'BAR00000016', 4, 428.14, 613.36, 256.88, '蓝色', 'S', '', 36, 4.19, 'kg', '', '[{"price": 342.51, "minQuantity": 10}]', '', NULL, '2025-09-24 12:17:09', '2025-10-15 12:17:09', '1', '1', '2025-09-24 12:17:09', '1', '2025-10-15 12:17:09', 0), +(17, 5, 1, 'sku-17', 'spu-5', '', '白色 / L', 'SKU-5-1', 'BAR00000017', 1, 250.44, 304.72, 150.26, '白色', 'L', '', 82, 3.73, 'kg', '', '[{"price": 200.35, "minQuantity": 10}]', '', NULL, '2025-05-19 12:17:09', '2025-05-29 12:17:09', '1', '1', '2025-05-19 12:17:09', '1', '2025-05-29 12:17:09', 0), +(18, 5, 1, 'sku-18', 'spu-5', '', '绿色 / S', 'SKU-5-2', 'BAR00000018', 2, 276.79, 404.72, 166.07, '绿色', 'S', '', 81, 2.9, 'kg', '', '[{"price": 221.43, "minQuantity": 10}]', '', NULL, '2025-05-05 12:17:09', '2025-05-16 12:17:09', '1', '1', '2025-05-05 12:17:09', '1', '2025-05-16 12:17:09', 0), +(19, 5, 1, 'sku-19', 'spu-5', '', '蓝色 / XXL', 'SKU-5-3', 'BAR00000019', 3, 238.73, 336.81, 143.24, '蓝色', 'XXL', '', 8, 0.62, 'kg', '', '[{"price": 190.99, "minQuantity": 10}]', '', NULL, '2025-06-21 12:17:09', '2025-06-29 12:17:09', '1', '1', '2025-06-21 12:17:09', '1', '2025-06-29 12:17:09', 0), +(20, 5, 1, 'sku-20', 'spu-5', '', '蓝色 / L', 'SKU-5-4', 'BAR00000020', 4, 279.88, 413.66, 167.93, '蓝色', 'L', '', 42, 2.78, 'kg', '', '[{"price": 223.9, "minQuantity": 10}]', '', NULL, '2025-09-11 12:17:09', '2025-10-09 12:17:09', '1', '1', '2025-09-11 12:17:09', '1', '2025-10-09 12:17:09', 0), +(21, 6, 1, 'sku-21', 'spu-6', '', '蓝色 / L', 'SKU-6-1', 'BAR00000021', 1, 527.6, 710.02, 316.56, '蓝色', 'L', '', 32, 4.36, 'kg', '', '[{"price": 422.08, "minQuantity": 10}]', '', NULL, '2024-11-29 12:17:09', '2024-12-18 12:17:09', '1', '1', '2024-11-29 12:17:09', '1', '2024-12-18 12:17:09', 0), +(22, 6, 1, 'sku-22', 'spu-6', '', '白色 / L', 'SKU-6-2', 'BAR00000022', 2, 516.8, 770.98, 310.08, '白色', 'L', '', 69, 1.83, 'kg', '', '[{"price": 413.44, "minQuantity": 10}]', '', NULL, '2025-05-22 12:17:09', '2025-06-12 12:17:09', '1', '1', '2025-05-22 12:17:09', '1', '2025-06-12 12:17:09', 0), +(23, 6, 1, 'sku-23', 'spu-6', '', '红色 / S', 'SKU-6-3', 'BAR00000023', 3, 485.59, 598.04, 291.36, '红色', 'S', '', 79, 3.85, 'kg', '', '[{"price": 388.47, "minQuantity": 10}]', '', NULL, '2024-11-24 12:17:09', '2024-12-17 12:17:09', '1', '1', '2024-11-24 12:17:09', '1', '2024-12-17 12:17:09', 0), +(24, 7, 1, 'sku-24', 'spu-7', '', '蓝色 / XXL', 'SKU-7-1', 'BAR00000024', 1, 161.95, 231.09, 97.17, '蓝色', 'XXL', '', 49, 4.62, 'kg', '', '[{"price": 129.56, "minQuantity": 10}]', '', NULL, '2025-04-20 12:17:09', '2025-04-24 12:17:09', '1', '1', '2025-04-20 12:17:09', '1', '2025-04-24 12:17:09', 0), +(25, 7, 1, 'sku-25', 'spu-7', '', '黑色 / S', 'SKU-7-2', 'BAR00000025', 2, 148.66, 211.66, 89.2, '黑色', 'S', '', 20, 1.5, 'kg', '', '[{"price": 118.93, "minQuantity": 10}]', '', NULL, '2025-04-28 12:17:09', '2025-05-16 12:17:09', '1', '1', '2025-04-28 12:17:09', '1', '2025-05-16 12:17:09', 0), +(26, 7, 1, 'sku-26', 'spu-7', '', '黑色 / XXL', 'SKU-7-3', 'BAR00000026', 3, 173.53, 213.88, 104.12, '黑色', 'XXL', '', 2, 4.43, 'kg', '', '[{"price": 138.82, "minQuantity": 10}]', '', NULL, '2024-12-16 12:17:09', '2024-12-17 12:17:09', '1', '1', '2024-12-16 12:17:09', '1', '2024-12-17 12:17:09', 0), +(27, 7, 1, 'sku-27', 'spu-7', '', '黑色 / S', 'SKU-7-4', 'BAR00000027', 4, 177.7, 233.07, 106.62, '黑色', 'S', '', 73, 2.65, 'kg', '', '[{"price": 142.16, "minQuantity": 10}]', '', NULL, '2025-08-29 12:17:09', '2025-09-23 12:17:09', '1', '1', '2025-08-29 12:17:09', '1', '2025-09-23 12:17:09', 0), +(28, 8, 1, 'sku-28', 'spu-8', '', '白色 / XL', 'SKU-8-1', 'BAR00000028', 1, 471.42, 690.0, 282.85, '白色', 'XL', '', 72, 1.76, 'kg', '', '[{"price": 377.13, "minQuantity": 10}]', '', NULL, '2025-01-31 12:17:09', '2025-02-17 12:17:09', '1', '1', '2025-01-31 12:17:09', '1', '2025-02-17 12:17:09', 0), +(29, 8, 1, 'sku-29', 'spu-8', '', '黑色 / S', 'SKU-8-2', 'BAR00000029', 2, 445.7, 585.74, 267.42, '黑色', 'S', '', 62, 0.59, 'kg', '', '[{"price": 356.56, "minQuantity": 10}]', '', NULL, '2025-04-05 12:17:09', '2025-04-25 12:17:09', '1', '1', '2025-04-05 12:17:09', '1', '2025-04-25 12:17:09', 0), +(30, 8, 1, 'sku-30', 'spu-8', '', '灰色 / L', 'SKU-8-3', 'BAR00000030', 3, 477.89, 605.71, 286.74, '灰色', 'L', '', 1, 2.19, 'kg', '', '[{"price": 382.31, "minQuantity": 10}]', '', NULL, '2025-09-19 12:17:09', '2025-10-06 12:17:09', '1', '1', '2025-09-19 12:17:09', '1', '2025-10-06 12:17:09', 0), +(31, 9, 1, 'sku-31', 'spu-9', '', '红色 / XL', 'SKU-9-1', 'BAR00000031', 1, 432.85, 526.5, 259.71, '红色', 'XL', '', 44, 1.11, 'kg', '', '[{"price": 346.28, "minQuantity": 10}]', '', NULL, '2024-12-13 12:17:09', '2024-12-15 12:17:09', '1', '1', '2024-12-13 12:17:09', '1', '2024-12-15 12:17:09', 0), +(32, 9, 1, 'sku-32', 'spu-9', '', '红色 / XXL', 'SKU-9-2', 'BAR00000032', 2, 448.02, 597.6, 268.81, '红色', 'XXL', '', 18, 4.56, 'kg', '', '[{"price": 358.42, "minQuantity": 10}]', '', NULL, '2025-08-19 12:17:09', '2025-09-17 12:17:09', '1', '1', '2025-08-19 12:17:09', '1', '2025-09-17 12:17:09', 0), +(33, 9, 1, 'sku-33', 'spu-9', '', '黑色 / XXL', 'SKU-9-3', 'BAR00000033', 3, 423.8, 631.05, 254.28, '黑色', 'XXL', '', 21, 5.0, 'kg', '', '[{"price": 339.04, "minQuantity": 10}]', '', NULL, '2025-05-29 12:17:09', '2025-06-05 12:17:09', '1', '1', '2025-05-29 12:17:09', '1', '2025-06-05 12:17:09', 0), +(34, 9, 1, 'sku-34', 'spu-9', '', '灰色 / XXL', 'SKU-9-4', 'BAR00000034', 4, 424.56, 557.45, 254.73, '灰色', 'XXL', '', 70, 0.17, 'kg', '', '[{"price": 339.64, "minQuantity": 10}]', '', NULL, '2025-05-30 12:17:09', '2025-06-11 12:17:09', '1', '1', '2025-05-30 12:17:09', '1', '2025-06-11 12:17:09', 0), +(35, 9, 1, 'sku-35', 'spu-9', '', '绿色 / XXL', 'SKU-9-5', 'BAR00000035', 5, 441.55, 568.31, 264.93, '绿色', 'XXL', '', 44, 1.73, 'kg', '', '[{"price": 353.24, "minQuantity": 10}]', '', NULL, '2025-03-09 12:17:09', '2025-03-15 12:17:09', '1', '1', '2025-03-09 12:17:09', '1', '2025-03-15 12:17:09', 0), +(36, 10, 1, 'sku-36', 'spu-10', '', '绿色', 'SKU-10-1', 'BAR00000036', 1, 99.88, 120.43, 59.93, '绿色', '', '', 98, 1.93, 'kg', '', '[{"price": 79.9, "minQuantity": 10}]', '', NULL, '2024-12-25 12:17:09', '2025-01-09 12:17:09', '1', '1', '2024-12-25 12:17:09', '1', '2025-01-09 12:17:09', 0), +(37, 10, 1, 'sku-37', 'spu-10', '', '蓝色', 'SKU-10-2', 'BAR00000037', 2, 110.96, 140.29, 66.58, '蓝色', '', '', 100, 1.37, 'kg', '', '[{"price": 88.77, "minQuantity": 10}]', '', NULL, '2025-05-10 12:17:09', '2025-05-26 12:17:09', '1', '1', '2025-05-10 12:17:09', '1', '2025-05-26 12:17:09', 0); diff --git a/utils/db_connector.py b/utils/db_connector.py index 39d942e..f1bf39c 100644 --- a/utils/db_connector.py +++ b/utils/db_connector.py @@ -2,7 +2,7 @@ Database connector utility for MySQL connections. """ -from sqlalchemy import create_engine +from sqlalchemy import create_engine, text from sqlalchemy.pool import QueuePool from typing import Dict, Any, Optional import pymysql @@ -84,7 +84,8 @@ def test_connection(engine) -> bool: """ try: with engine.connect() as conn: - conn.execute("SELECT 1") + conn.execute(text("SELECT 1")) + conn.commit() return True except Exception as e: print(f"Database connection test failed: {e}") -- libgit2 0.21.2