Commit fb68a0efdfba85b011ef38ac4e26a56c919b97e0
1 parent
1852e3e3
配置优化
Showing
10 changed files
with
770 additions
and
48 deletions
Show diff stats
| 1 | 1 | # Elasticsearch Configuration |
| 2 | 2 | ES_HOST=http://localhost:9200 |
| 3 | -ES_USERNAME= | |
| 4 | -ES_PASSWORD= | |
| 3 | +ES_USERNAME=essa | |
| 4 | +ES_PASSWORD=4hOaLaf41y2VuI8y | |
| 5 | 5 | |
| 6 | 6 | # Redis Configuration (Optional) |
| 7 | 7 | REDIS_HOST=localhost |
| 8 | 8 | REDIS_PORT=6479 |
| 9 | -REDIS_PASSWORD= | |
| 9 | +REDIS_PASSWORD=BMfv5aI31kgHWtlx | |
| 10 | 10 | |
| 11 | 11 | # DeepL Translation API |
| 12 | -DEEPL_AUTH_KEY= | |
| 12 | +DEEPL_AUTH_KEY=c9293ab4-ad25-479b-919f-ab4e63b429ed | |
| 13 | 13 | |
| 14 | 14 | # Customer Configuration |
| 15 | 15 | CUSTOMER_ID=customer1 | ... | ... |
.env.example
| 1 | -# Environment Configuration | |
| 1 | +# Environment Configuration Template | |
| 2 | 2 | # Copy this file to .env and update with your actual values |
| 3 | 3 | |
| 4 | 4 | # Elasticsearch Configuration (v8.18) |
| 5 | 5 | ES_HOST=http://localhost:9200 |
| 6 | -ES_USERNAME=essa | |
| 7 | -ES_PASSWORD=4hOaLaf41y2VuI8y | |
| 6 | +ES_USERNAME= | |
| 7 | +ES_PASSWORD= | |
| 8 | 8 | |
| 9 | 9 | # Redis Configuration (for caching) |
| 10 | 10 | REDIS_HOST=localhost |
| 11 | 11 | REDIS_PORT=6479 |
| 12 | -REDIS_PASSWORD=BMfv5aI31kgHWtlx | |
| 12 | +REDIS_PASSWORD= | |
| 13 | 13 | |
| 14 | 14 | # DeepL Translation API |
| 15 | -DEEPL_AUTH_KEY=c9293ab4-ad25-479b-919f-ab4e63b429ed | |
| 15 | +DEEPL_AUTH_KEY= | |
| 16 | 16 | |
| 17 | 17 | # Customer Configuration |
| 18 | 18 | CUSTOMER_ID=customer1 |
| ... | ... | @@ -27,3 +27,10 @@ IMAGE_MODEL_DIR=/data/tw/models/cn-clip |
| 27 | 27 | |
| 28 | 28 | # Cache Directory |
| 29 | 29 | CACHE_DIR=.cache |
| 30 | + | |
| 31 | +# MySQL Database Configuration (Shoplazza) | |
| 32 | +DB_HOST= | |
| 33 | +DB_PORT=3306 | |
| 34 | +DB_DATABASE= | |
| 35 | +DB_USERNAME= | |
| 36 | +DB_PASSWORD= | ... | ... |
config/env_config.py
| ... | ... | @@ -47,11 +47,11 @@ CACHE_DIR = os.getenv('CACHE_DIR', '.cache') |
| 47 | 47 | |
| 48 | 48 | # MySQL Database Configuration (Shoplazza) |
| 49 | 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'), | |
| 50 | + 'host': os.getenv('DB_HOST'), | |
| 51 | + 'port': int(os.getenv('DB_PORT', 3306)) if os.getenv('DB_PORT') else 3306, | |
| 52 | + 'database': os.getenv('DB_DATABASE'), | |
| 53 | + 'username': os.getenv('DB_USERNAME'), | |
| 54 | + 'password': os.getenv('DB_PASSWORD'), | |
| 55 | 55 | } |
| 56 | 56 | |
| 57 | 57 | ... | ... |
| ... | ... | @@ -0,0 +1,576 @@ |
| 1 | +// SearchEngine Frontend - Modern UI | |
| 2 | + | |
| 3 | +const TENANT_ID = '1'; | |
| 4 | +const API_BASE_URL = 'http://localhost:6002'; | |
| 5 | +document.getElementById('apiUrl').textContent = API_BASE_URL; | |
| 6 | + | |
| 7 | +// State Management | |
| 8 | +let state = { | |
| 9 | + query: '', | |
| 10 | + currentPage: 1, | |
| 11 | + pageSize: 20, | |
| 12 | + totalResults: 0, | |
| 13 | + filters: {}, | |
| 14 | + rangeFilters: {}, | |
| 15 | + sortBy: '', | |
| 16 | + sortOrder: 'desc', | |
| 17 | + facets: null, | |
| 18 | + lastSearchData: null, | |
| 19 | + debug: true // Always enable debug mode for test frontend | |
| 20 | +}; | |
| 21 | + | |
| 22 | +// Initialize | |
| 23 | +document.addEventListener('DOMContentLoaded', function() { | |
| 24 | + console.log('SearchEngine loaded'); | |
| 25 | + console.log('Debug mode: always enabled (test frontend)'); | |
| 26 | + | |
| 27 | + document.getElementById('searchInput').focus(); | |
| 28 | +}); | |
| 29 | + | |
| 30 | +// Keyboard handler | |
| 31 | +function handleKeyPress(event) { | |
| 32 | + if (event.key === 'Enter') { | |
| 33 | + performSearch(); | |
| 34 | + } | |
| 35 | +} | |
| 36 | + | |
| 37 | +// Toggle filters visibility | |
| 38 | +function toggleFilters() { | |
| 39 | + const filterSection = document.getElementById('filterSection'); | |
| 40 | + filterSection.classList.toggle('hidden'); | |
| 41 | +} | |
| 42 | + | |
| 43 | +// Perform search | |
| 44 | +async function performSearch(page = 1) { | |
| 45 | + const query = document.getElementById('searchInput').value.trim(); | |
| 46 | + | |
| 47 | + if (!query) { | |
| 48 | + alert('Please enter search keywords'); | |
| 49 | + return; | |
| 50 | + } | |
| 51 | + | |
| 52 | + state.query = query; | |
| 53 | + state.currentPage = page; | |
| 54 | + state.pageSize = parseInt(document.getElementById('resultSize').value); | |
| 55 | + | |
| 56 | + const from = (page - 1) * state.pageSize; | |
| 57 | + | |
| 58 | + // Define facets (简化配置) | |
| 59 | + const facets = [ | |
| 60 | + { | |
| 61 | + "field": "category_keyword", | |
| 62 | + "size": 15, | |
| 63 | + "type": "terms" | |
| 64 | + }, | |
| 65 | + { | |
| 66 | + "field": "vendor_keyword", | |
| 67 | + "size": 15, | |
| 68 | + "type": "terms" | |
| 69 | + }, | |
| 70 | + { | |
| 71 | + "field": "tags_keyword", | |
| 72 | + "size": 10, | |
| 73 | + "type": "terms" | |
| 74 | + }, | |
| 75 | + { | |
| 76 | + "field": "min_price", | |
| 77 | + "type": "range", | |
| 78 | + "ranges": [ | |
| 79 | + {"key": "0-50", "to": 50}, | |
| 80 | + {"key": "50-100", "from": 50, "to": 100}, | |
| 81 | + {"key": "100-200", "from": 100, "to": 200}, | |
| 82 | + {"key": "200+", "from": 200} | |
| 83 | + ] | |
| 84 | + } | |
| 85 | + ]; | |
| 86 | + | |
| 87 | + // Show loading | |
| 88 | + document.getElementById('loading').style.display = 'block'; | |
| 89 | + document.getElementById('productGrid').innerHTML = ''; | |
| 90 | + | |
| 91 | + try { | |
| 92 | + const response = await fetch(`${API_BASE_URL}/search/`, { | |
| 93 | + method: 'POST', | |
| 94 | + headers: { | |
| 95 | + 'Content-Type': 'application/json', | |
| 96 | + 'X-Tenant-ID': TENANT_ID, | |
| 97 | + }, | |
| 98 | + body: JSON.stringify({ | |
| 99 | + query: query, | |
| 100 | + size: state.pageSize, | |
| 101 | + from: from, | |
| 102 | + filters: Object.keys(state.filters).length > 0 ? state.filters : null, | |
| 103 | + range_filters: Object.keys(state.rangeFilters).length > 0 ? state.rangeFilters : null, | |
| 104 | + facets: facets, | |
| 105 | + sort_by: state.sortBy || null, | |
| 106 | + sort_order: state.sortOrder, | |
| 107 | + debug: state.debug | |
| 108 | + }) | |
| 109 | + }); | |
| 110 | + | |
| 111 | + if (!response.ok) { | |
| 112 | + throw new Error(`HTTP ${response.status}: ${response.statusText}`); | |
| 113 | + } | |
| 114 | + | |
| 115 | + const data = await response.json(); | |
| 116 | + state.lastSearchData = data; | |
| 117 | + state.totalResults = data.total; | |
| 118 | + state.facets = data.facets; | |
| 119 | + | |
| 120 | + displayResults(data); | |
| 121 | + displayFacets(data.facets); | |
| 122 | + displayPagination(); | |
| 123 | + displayDebugInfo(data); | |
| 124 | + updateProductCount(data.total); | |
| 125 | + updateClearFiltersButton(); | |
| 126 | + | |
| 127 | + } catch (error) { | |
| 128 | + console.error('Search error:', error); | |
| 129 | + document.getElementById('productGrid').innerHTML = ` | |
| 130 | + <div class="error-message"> | |
| 131 | + <strong>Search Error:</strong> ${error.message} | |
| 132 | + <br><br> | |
| 133 | + <small>Please ensure backend service is running (${API_BASE_URL})</small> | |
| 134 | + </div> | |
| 135 | + `; | |
| 136 | + } finally { | |
| 137 | + document.getElementById('loading').style.display = 'none'; | |
| 138 | + } | |
| 139 | +} | |
| 140 | + | |
| 141 | +// Display results in grid | |
| 142 | +function displayResults(data) { | |
| 143 | + const grid = document.getElementById('productGrid'); | |
| 144 | + | |
| 145 | + if (!data.results || data.results.length === 0) { | |
| 146 | + grid.innerHTML = ` | |
| 147 | + <div class="no-results" style="grid-column: 1 / -1;"> | |
| 148 | + <h3>No Results Found</h3> | |
| 149 | + <p>Try different keywords or filters</p> | |
| 150 | + </div> | |
| 151 | + `; | |
| 152 | + return; | |
| 153 | + } | |
| 154 | + | |
| 155 | + let html = ''; | |
| 156 | + | |
| 157 | + data.results.forEach((hit) => { | |
| 158 | + const score = product.relevance_score; | |
| 159 | + html += ` | |
| 160 | + <div class="product-card"> | |
| 161 | + <div class="product-image-wrapper"> | |
| 162 | + ${product.image_url ? ` | |
| 163 | + <img src="${escapeHtml(product.image_url)}" | |
| 164 | + alt="${escapeHtml(product.title)}" | |
| 165 | + class="product-image" | |
| 166 | + onerror="this.src='data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%22100%22 height=%22100%22%3E%3Crect fill=%22%23f0f0f0%22 width=%22100%22 height=%22100%22/%3E%3Ctext x=%2250%25%22 y=%2250%25%22 font-size=%2214%22 text-anchor=%22middle%22 dy=%22.3em%22 fill=%22%23999%22%3ENo Image%3C/text%3E%3C/svg%3E'"> | |
| 167 | + ` : ` | |
| 168 | + <div style="color: #ccc; font-size: 14px;">No Image</div> | |
| 169 | + `} | |
| 170 | + </div> | |
| 171 | + | |
| 172 | + <div class="product-price"> | |
| 173 | + ${product.price ? `$${product.price.toFixed(2)}` : 'N/A'}${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>` : \'\'} | |
| 174 | + </div> | |
| 175 | + | |
| 176 | + <div class="product-stock"> | |
| 177 | + ${product.in_stock ? '<span style="color: green;">In Stock</span>' : '<span style="color: red;">Out of Stock</span>'} | |
| 178 | + ${product.variants && product.variants.length > 0 ? `<span style="color: #666; font-size: 0.9em;">(${product.variants.length} variants)</span>` : ''} | |
| 179 | + </div> | |
| 180 | + | |
| 181 | + <div class="product-title"> | |
| 182 | + ${escapeHtml(product.title || product.title || 'N/A')} | |
| 183 | + </div> | |
| 184 | + | |
| 185 | + <div class="product-meta">${product.vendor ? escapeHtml(product.vendor) : ''}${product.product_type ? ' | ' + escapeHtml(product.product_type) : ''}${product.category ? ' | ' + escapeHtml(product.category) : ''} ${product.tags ? ` | |
| 186 | + <div class="product-tags"> | |
| 187 | + Tags: ${escapeHtml(product.tags)} | |
| 188 | + </div> | |
| 189 | + ` : ''} | |
| 190 | + </div> | |
| 191 | + `; | |
| 192 | + }); | |
| 193 | + | |
| 194 | + grid.innerHTML = html; | |
| 195 | +} | |
| 196 | + | |
| 197 | +// Display facets as filter tags (重构版 - 标准化格式) | |
| 198 | +function displayFacets(facets) { | |
| 199 | + if (!facets) return; | |
| 200 | + | |
| 201 | + facets.forEach(facet => { | |
| 202 | + // 根据字段名找到对应的容器 | |
| 203 | + let containerId = null; | |
| 204 | + let maxDisplay = 10; | |
| 205 | + | |
| 206 | + if (facet.field === 'category_keyword') { | |
| 207 | + containerId = 'categoryTags'; | |
| 208 | + maxDisplay = 10; | |
| 209 | + } else if (facet.field === 'vendor_keyword') { | |
| 210 | + containerId = 'brandTags'; | |
| 211 | + maxDisplay = 10; | |
| 212 | + } else if (facet.field === 'tags_keyword') { | |
| 213 | + containerId = 'supplierTags'; | |
| 214 | + maxDisplay = 8; | |
| 215 | + } | |
| 216 | + | |
| 217 | + if (!containerId) return; | |
| 218 | + | |
| 219 | + const container = document.getElementById(containerId); | |
| 220 | + if (!container) return; | |
| 221 | + | |
| 222 | + let html = ''; | |
| 223 | + | |
| 224 | + // 渲染分面值 | |
| 225 | + facet.values.slice(0, maxDisplay).forEach(facetValue => { | |
| 226 | + const value = facetValue.value; | |
| 227 | + const count = facetValue.count; | |
| 228 | + const selected = facetValue.selected; | |
| 229 | + | |
| 230 | + html += ` | |
| 231 | + <span class="filter-tag ${selected ? 'active' : ''}" | |
| 232 | + onclick="toggleFilter('${escapeAttr(facet.field)}', '${escapeAttr(value)}')"> | |
| 233 | + ${escapeHtml(value)} (${count}) | |
| 234 | + </span> | |
| 235 | + `; | |
| 236 | + }); | |
| 237 | + | |
| 238 | + container.innerHTML = html; | |
| 239 | + }); | |
| 240 | +} | |
| 241 | + | |
| 242 | +// Toggle filter | |
| 243 | +function toggleFilter(field, value) { | |
| 244 | + if (!state.filters[field]) { | |
| 245 | + state.filters[field] = []; | |
| 246 | + } | |
| 247 | + | |
| 248 | + const index = state.filters[field].indexOf(value); | |
| 249 | + if (index > -1) { | |
| 250 | + state.filters[field].splice(index, 1); | |
| 251 | + if (state.filters[field].length === 0) { | |
| 252 | + delete state.filters[field]; | |
| 253 | + } | |
| 254 | + } else { | |
| 255 | + state.filters[field].push(value); | |
| 256 | + } | |
| 257 | + | |
| 258 | + performSearch(1); // Reset to page 1 | |
| 259 | +} | |
| 260 | + | |
| 261 | +// Handle price filter (重构版 - 使用 rangeFilters) | |
| 262 | +function handlePriceFilter(value) { | |
| 263 | + if (!value) { | |
| 264 | + delete state.rangeFilters.price; | |
| 265 | + } else { | |
| 266 | + const priceRanges = { | |
| 267 | + '0-50': { lt: 50 }, | |
| 268 | + '50-100': { gte: 50, lt: 100 }, | |
| 269 | + '100-200': { gte: 100, lt: 200 }, | |
| 270 | + '200+': { gte: 200 } | |
| 271 | + }; | |
| 272 | + | |
| 273 | + if (priceRanges[value]) { | |
| 274 | + state.rangeFilters.price = priceRanges[value]; | |
| 275 | + } | |
| 276 | + } | |
| 277 | + | |
| 278 | + performSearch(1); | |
| 279 | +} | |
| 280 | + | |
| 281 | +// Handle time filter (重构版 - 使用 rangeFilters) | |
| 282 | +function handleTimeFilter(value) { | |
| 283 | + if (!value) { | |
| 284 | + delete state.rangeFilters.create_time; | |
| 285 | + } else { | |
| 286 | + const now = new Date(); | |
| 287 | + let fromDate; | |
| 288 | + | |
| 289 | + switch(value) { | |
| 290 | + case 'today': | |
| 291 | + fromDate = new Date(now.setHours(0, 0, 0, 0)); | |
| 292 | + break; | |
| 293 | + case 'week': | |
| 294 | + fromDate = new Date(now.setDate(now.getDate() - 7)); | |
| 295 | + break; | |
| 296 | + case 'month': | |
| 297 | + fromDate = new Date(now.setMonth(now.getMonth() - 1)); | |
| 298 | + break; | |
| 299 | + case '3months': | |
| 300 | + fromDate = new Date(now.setMonth(now.getMonth() - 3)); | |
| 301 | + break; | |
| 302 | + case '6months': | |
| 303 | + fromDate = new Date(now.setMonth(now.getMonth() - 6)); | |
| 304 | + break; | |
| 305 | + } | |
| 306 | + | |
| 307 | + if (fromDate) { | |
| 308 | + state.rangeFilters.create_time = { | |
| 309 | + gte: fromDate.toISOString() | |
| 310 | + }; | |
| 311 | + } | |
| 312 | + } | |
| 313 | + | |
| 314 | + performSearch(1); | |
| 315 | +} | |
| 316 | + | |
| 317 | +// Clear all filters | |
| 318 | +function clearAllFilters() { | |
| 319 | + state.filters = {}; | |
| 320 | + state.rangeFilters = {}; | |
| 321 | + document.getElementById('priceFilter').value = ''; | |
| 322 | + document.getElementById('timeFilter').value = ''; | |
| 323 | + performSearch(1); | |
| 324 | +} | |
| 325 | + | |
| 326 | +// Update clear filters button visibility | |
| 327 | +function updateClearFiltersButton() { | |
| 328 | + const btn = document.getElementById('clearFiltersBtn'); | |
| 329 | + if (Object.keys(state.filters).length > 0 || Object.keys(state.rangeFilters).length > 0) { | |
| 330 | + btn.style.display = 'inline-block'; | |
| 331 | + } else { | |
| 332 | + btn.style.display = 'none'; | |
| 333 | + } | |
| 334 | +} | |
| 335 | + | |
| 336 | +// Update product count | |
| 337 | +function updateProductCount(total) { | |
| 338 | + document.getElementById('productCount').textContent = `${total.toLocaleString()} products found`; | |
| 339 | +} | |
| 340 | + | |
| 341 | +// Sort functions | |
| 342 | +function setSortByDefault() { | |
| 343 | + // Remove active from all buttons and arrows | |
| 344 | + document.querySelectorAll('.sort-btn').forEach(b => b.classList.remove('active')); | |
| 345 | + document.querySelectorAll('.arrow-up, .arrow-down').forEach(a => a.classList.remove('active')); | |
| 346 | + | |
| 347 | + // Set default button active | |
| 348 | + const defaultBtn = document.querySelector('.sort-btn[data-sort=""]'); | |
| 349 | + if (defaultBtn) defaultBtn.classList.add('active'); | |
| 350 | + | |
| 351 | + state.sortBy = ''; | |
| 352 | + state.sortOrder = 'desc'; | |
| 353 | + | |
| 354 | + performSearch(1); | |
| 355 | +} | |
| 356 | + | |
| 357 | +function sortByField(field, order) { | |
| 358 | + state.sortBy = field; | |
| 359 | + state.sortOrder = order; | |
| 360 | + | |
| 361 | + // Remove active from all buttons (but keep "By default" if no sort) | |
| 362 | + document.querySelectorAll('.sort-btn').forEach(b => b.classList.remove('active')); | |
| 363 | + | |
| 364 | + // Remove active from all arrows | |
| 365 | + document.querySelectorAll('.arrow-up, .arrow-down').forEach(a => a.classList.remove('active')); | |
| 366 | + | |
| 367 | + // Add active to clicked arrow | |
| 368 | + const activeArrow = document.querySelector(`.arrow-up[data-field="${field}"][data-order="${order}"], .arrow-down[data-field="${field}"][data-order="${order}"]`); | |
| 369 | + if (activeArrow) { | |
| 370 | + activeArrow.classList.add('active'); | |
| 371 | + } | |
| 372 | + | |
| 373 | + performSearch(state.currentPage); | |
| 374 | +} | |
| 375 | + | |
| 376 | +// Pagination | |
| 377 | +function displayPagination() { | |
| 378 | + const paginationDiv = document.getElementById('pagination'); | |
| 379 | + | |
| 380 | + if (state.totalResults <= state.pageSize) { | |
| 381 | + paginationDiv.style.display = 'none'; | |
| 382 | + return; | |
| 383 | + } | |
| 384 | + | |
| 385 | + paginationDiv.style.display = 'flex'; | |
| 386 | + | |
| 387 | + const totalPages = Math.ceil(state.totalResults / state.pageSize); | |
| 388 | + const currentPage = state.currentPage; | |
| 389 | + | |
| 390 | + let html = ` | |
| 391 | + <button class="page-btn" onclick="goToPage(${currentPage - 1})" | |
| 392 | + ${currentPage === 1 ? 'disabled' : ''}> | |
| 393 | + ← Previous | |
| 394 | + </button> | |
| 395 | + `; | |
| 396 | + | |
| 397 | + // Page numbers | |
| 398 | + const maxVisible = 5; | |
| 399 | + let startPage = Math.max(1, currentPage - Math.floor(maxVisible / 2)); | |
| 400 | + let endPage = Math.min(totalPages, startPage + maxVisible - 1); | |
| 401 | + | |
| 402 | + if (endPage - startPage < maxVisible - 1) { | |
| 403 | + startPage = Math.max(1, endPage - maxVisible + 1); | |
| 404 | + } | |
| 405 | + | |
| 406 | + if (startPage > 1) { | |
| 407 | + html += `<button class="page-btn" onclick="goToPage(1)">1</button>`; | |
| 408 | + if (startPage > 2) { | |
| 409 | + html += `<span class="page-info">...</span>`; | |
| 410 | + } | |
| 411 | + } | |
| 412 | + | |
| 413 | + for (let i = startPage; i <= endPage; i++) { | |
| 414 | + html += ` | |
| 415 | + <button class="page-btn ${i === currentPage ? 'active' : ''}" | |
| 416 | + onclick="goToPage(${i})"> | |
| 417 | + ${i} | |
| 418 | + </button> | |
| 419 | + `; | |
| 420 | + } | |
| 421 | + | |
| 422 | + if (endPage < totalPages) { | |
| 423 | + if (endPage < totalPages - 1) { | |
| 424 | + html += `<span class="page-info">...</span>`; | |
| 425 | + } | |
| 426 | + html += `<button class="page-btn" onclick="goToPage(${totalPages})">${totalPages}</button>`; | |
| 427 | + } | |
| 428 | + | |
| 429 | + html += ` | |
| 430 | + <button class="page-btn" onclick="goToPage(${currentPage + 1})" | |
| 431 | + ${currentPage === totalPages ? 'disabled' : ''}> | |
| 432 | + Next → | |
| 433 | + </button> | |
| 434 | + `; | |
| 435 | + | |
| 436 | + html += ` | |
| 437 | + <span class="page-info"> | |
| 438 | + Page ${currentPage} of ${totalPages} (${state.totalResults.toLocaleString()} results) | |
| 439 | + </span> | |
| 440 | + `; | |
| 441 | + | |
| 442 | + paginationDiv.innerHTML = html; | |
| 443 | +} | |
| 444 | + | |
| 445 | +function goToPage(page) { | |
| 446 | + const totalPages = Math.ceil(state.totalResults / state.pageSize); | |
| 447 | + if (page < 1 || page > totalPages) return; | |
| 448 | + | |
| 449 | + performSearch(page); | |
| 450 | + | |
| 451 | + // Scroll to top | |
| 452 | + window.scrollTo({ top: 0, behavior: 'smooth' }); | |
| 453 | +} | |
| 454 | + | |
| 455 | +// Display debug info | |
| 456 | +function displayDebugInfo(data) { | |
| 457 | + const debugInfoDiv = document.getElementById('debugInfo'); | |
| 458 | + | |
| 459 | + if (!state.debug || !data.debug_info) { | |
| 460 | + // If debug mode is off or no debug info, show basic query info | |
| 461 | + if (data.query_info) { | |
| 462 | + let html = '<div style="padding: 10px;">'; | |
| 463 | + html += `<div><strong>查询:</strong> ${escapeHtml(data.query_info.original_query || 'N/A')}</div>`; | |
| 464 | + html += `<div><strong>语言:</strong> ${getLanguageName(data.query_info.detected_language)}</div>`; | |
| 465 | + html += '</div>'; | |
| 466 | + debugInfoDiv.innerHTML = html; | |
| 467 | + } else { | |
| 468 | + debugInfoDiv.innerHTML = ''; | |
| 469 | + } | |
| 470 | + return; | |
| 471 | + } | |
| 472 | + | |
| 473 | + // Display comprehensive debug info when debug mode is on | |
| 474 | + const debugInfo = data.debug_info; | |
| 475 | + let html = '<div style="padding: 10px; font-family: monospace; font-size: 12px;">'; | |
| 476 | + | |
| 477 | + // Query Analysis | |
| 478 | + if (debugInfo.query_analysis) { | |
| 479 | + html += '<div style="margin-bottom: 15px;"><strong style="font-size: 14px;">查询分析:</strong>'; | |
| 480 | + html += `<div>原始查询: ${escapeHtml(debugInfo.query_analysis.original_query || 'N/A')}</div>`; | |
| 481 | + html += `<div>标准化: ${escapeHtml(debugInfo.query_analysis.normalized_query || 'N/A')}</div>`; | |
| 482 | + html += `<div>重写后: ${escapeHtml(debugInfo.query_analysis.rewritten_query || 'N/A')}</div>`; | |
| 483 | + html += `<div>检测语言: ${getLanguageName(debugInfo.query_analysis.detected_language)}</div>`; | |
| 484 | + html += `<div>域: ${escapeHtml(debugInfo.query_analysis.domain || 'default')}</div>`; | |
| 485 | + html += `<div>简单查询: ${debugInfo.query_analysis.is_simple_query ? '是' : '否'}</div>`; | |
| 486 | + | |
| 487 | + if (debugInfo.query_analysis.translations && Object.keys(debugInfo.query_analysis.translations).length > 0) { | |
| 488 | + html += '<div>翻译: '; | |
| 489 | + for (const [lang, translation] of Object.entries(debugInfo.query_analysis.translations)) { | |
| 490 | + if (translation) { | |
| 491 | + html += `${getLanguageName(lang)}: ${escapeHtml(translation)}; `; | |
| 492 | + } | |
| 493 | + } | |
| 494 | + html += '</div>'; | |
| 495 | + } | |
| 496 | + | |
| 497 | + if (debugInfo.query_analysis.boolean_ast) { | |
| 498 | + html += `<div>布尔AST: ${escapeHtml(debugInfo.query_analysis.boolean_ast)}</div>`; | |
| 499 | + } | |
| 500 | + | |
| 501 | + html += `<div>向量嵌入: ${debugInfo.query_analysis.has_vector ? '已启用' : '未启用'}</div>`; | |
| 502 | + html += '</div>'; | |
| 503 | + } | |
| 504 | + | |
| 505 | + // Feature Flags | |
| 506 | + if (debugInfo.feature_flags) { | |
| 507 | + html += '<div style="margin-bottom: 15px;"><strong style="font-size: 14px;">功能开关:</strong>'; | |
| 508 | + html += `<div>翻译: ${debugInfo.feature_flags.translation_enabled ? '启用' : '禁用'}</div>`; | |
| 509 | + html += `<div>嵌入: ${debugInfo.feature_flags.embedding_enabled ? '启用' : '禁用'}</div>`; | |
| 510 | + html += `<div>重排序: ${debugInfo.feature_flags.rerank_enabled ? '启用' : '禁用'}</div>`; | |
| 511 | + html += '</div>'; | |
| 512 | + } | |
| 513 | + | |
| 514 | + // ES Response | |
| 515 | + if (debugInfo.es_response) { | |
| 516 | + html += '<div style="margin-bottom: 15px;"><strong style="font-size: 14px;">ES响应:</strong>'; | |
| 517 | + html += `<div>耗时: ${debugInfo.es_response.took_ms}ms</div>`; | |
| 518 | + html += `<div>总命中数: ${debugInfo.es_response.total_hits}</div>`; | |
| 519 | + html += `<div>最高分: ${debugInfo.es_response.max_score?.toFixed(3) || 0}</div>`; | |
| 520 | + html += '</div>'; | |
| 521 | + } | |
| 522 | + | |
| 523 | + // Stage Timings | |
| 524 | + if (debugInfo.stage_timings) { | |
| 525 | + html += '<div style="margin-bottom: 15px;"><strong style="font-size: 14px;">各阶段耗时:</strong>'; | |
| 526 | + for (const [stage, duration] of Object.entries(debugInfo.stage_timings)) { | |
| 527 | + html += `<div>${stage}: ${duration.toFixed(2)}ms</div>`; | |
| 528 | + } | |
| 529 | + html += '</div>'; | |
| 530 | + } | |
| 531 | + | |
| 532 | + // ES Query | |
| 533 | + if (debugInfo.es_query) { | |
| 534 | + html += '<div style="margin-bottom: 15px;"><strong style="font-size: 14px;">ES查询DSL:</strong>'; | |
| 535 | + html += `<pre style="background: #f5f5f5; padding: 10px; overflow: auto; max-height: 400px;">${escapeHtml(JSON.stringify(debugInfo.es_query, null, 2))}</pre>`; | |
| 536 | + html += '</div>'; | |
| 537 | + } | |
| 538 | + | |
| 539 | + html += '</div>'; | |
| 540 | + debugInfoDiv.innerHTML = html; | |
| 541 | +} | |
| 542 | + | |
| 543 | +// Helper functions | |
| 544 | +function escapeHtml(text) { | |
| 545 | + if (!text) return ''; | |
| 546 | + const div = document.createElement('div'); | |
| 547 | + div.textContent = text; | |
| 548 | + return div.innerHTML; | |
| 549 | +} | |
| 550 | + | |
| 551 | +function escapeAttr(text) { | |
| 552 | + if (!text) return ''; | |
| 553 | + return text.replace(/'/g, "\\'").replace(/"/g, '"'); | |
| 554 | +} | |
| 555 | + | |
| 556 | +function formatDate(dateStr) { | |
| 557 | + if (!dateStr) return ''; | |
| 558 | + try { | |
| 559 | + const date = new Date(dateStr); | |
| 560 | + return date.toLocaleDateString('zh-CN'); | |
| 561 | + } catch { | |
| 562 | + return dateStr; | |
| 563 | + } | |
| 564 | +} | |
| 565 | + | |
| 566 | +function getLanguageName(code) { | |
| 567 | + const names = { | |
| 568 | + 'zh': '中文', | |
| 569 | + 'en': 'English', | |
| 570 | + 'ru': 'Русский', | |
| 571 | + 'ar': 'العربية', | |
| 572 | + 'ja': '日本語', | |
| 573 | + 'unknown': 'Unknown' | |
| 574 | + }; | |
| 575 | + return names[code] || code; | |
| 576 | +} | ... | ... |
scripts/demo_base.sh
scripts/generate_test_data.py
| ... | ... | @@ -200,7 +200,7 @@ def generate_sku_data(spus: list, start_sku_id: int = 1): |
| 200 | 200 | 'image_src': '', |
| 201 | 201 | 'wholesale_price': '[{"price": ' + str(round(price * 0.8, 2)) + ', "minQuantity": 10}]', |
| 202 | 202 | 'note': '', |
| 203 | - 'extend': '', | |
| 203 | + 'extend': None, # JSON field, use NULL instead of empty string | |
| 204 | 204 | 'shoplazza_created_at': created_at.strftime('%Y-%m-%d %H:%M:%S'), |
| 205 | 205 | 'shoplazza_updated_at': updated_at.strftime('%Y-%m-%d %H:%M:%S'), |
| 206 | 206 | 'tenant_id': spu['tenant_id'], |
| ... | ... | @@ -216,6 +216,21 @@ def generate_sku_data(spus: list, start_sku_id: int = 1): |
| 216 | 216 | return skus |
| 217 | 217 | |
| 218 | 218 | |
| 219 | +def escape_sql_string(value: str) -> str: | |
| 220 | + """ | |
| 221 | + Escape SQL string value (replace single quotes with doubled quotes). | |
| 222 | + | |
| 223 | + Args: | |
| 224 | + value: String value to escape | |
| 225 | + | |
| 226 | + Returns: | |
| 227 | + Escaped string | |
| 228 | + """ | |
| 229 | + if value is None: | |
| 230 | + return '' | |
| 231 | + return str(value).replace("'", "''").replace("\\", "\\\\") | |
| 232 | + | |
| 233 | + | |
| 219 | 234 | def generate_sql_inserts(spus: list, skus: list, output_file: str): |
| 220 | 235 | """ |
| 221 | 236 | Generate SQL INSERT statements. |
| ... | ... | @@ -241,21 +256,26 @@ def generate_sql_inserts(spus: list, skus: list, output_file: str): |
| 241 | 256 | |
| 242 | 257 | for i, spu in enumerate(spus): |
| 243 | 258 | values = ( |
| 244 | - f"({spu['id']}, {spu['shop_id']}, '{spu['shoplazza_id']}', " | |
| 245 | - f"'{spu['handle']}', '{spu['title']}', '{spu['brief']}', " | |
| 246 | - f"'{spu['description']}', '{spu['spu']}', '{spu['vendor']}', " | |
| 247 | - f"'{spu['vendor_url']}', '{spu['seo_title']}', '{spu['seo_description']}', " | |
| 248 | - f"'{spu['seo_keywords']}', '{spu['image_src']}', {spu['image_width']}, " | |
| 249 | - f"{spu['image_height']}, '{spu['image_path']}', '{spu['image_alt']}', " | |
| 250 | - f"'{spu['inventory_policy']}', {spu['inventory_quantity']}, " | |
| 251 | - f"'{spu['inventory_tracking']}', {spu['published']}, " | |
| 252 | - f"'{spu['published_at']}', {spu['requires_shipping']}, {spu['taxable']}, " | |
| 259 | + f"({spu['id']}, {spu['shop_id']}, '{escape_sql_string(spu['shoplazza_id'])}', " | |
| 260 | + f"'{escape_sql_string(spu['handle'])}', '{escape_sql_string(spu['title'])}', " | |
| 261 | + f"'{escape_sql_string(spu['brief'])}', '{escape_sql_string(spu['description'])}', " | |
| 262 | + f"'{escape_sql_string(spu['spu'])}', '{escape_sql_string(spu['vendor'])}', " | |
| 263 | + f"'{escape_sql_string(spu['vendor_url'])}', '{escape_sql_string(spu['seo_title'])}', " | |
| 264 | + f"'{escape_sql_string(spu['seo_description'])}', '{escape_sql_string(spu['seo_keywords'])}', " | |
| 265 | + f"'{escape_sql_string(spu['image_src'])}', {spu['image_width']}, " | |
| 266 | + f"{spu['image_height']}, '{escape_sql_string(spu['image_path'])}', " | |
| 267 | + f"'{escape_sql_string(spu['image_alt'])}', '{escape_sql_string(spu['inventory_policy'])}', " | |
| 268 | + f"{spu['inventory_quantity']}, '{escape_sql_string(spu['inventory_tracking'])}', " | |
| 269 | + f"{spu['published']}, '{escape_sql_string(spu['published_at'])}', " | |
| 270 | + f"{spu['requires_shipping']}, {spu['taxable']}, " | |
| 253 | 271 | f"{spu['fake_sales']}, {spu['display_fake_sales']}, {spu['mixed_wholesale']}, " |
| 254 | 272 | f"{spu['need_variant_image']}, {spu['has_only_default_variant']}, " |
| 255 | - f"'{spu['tags']}', '{spu['note']}', '{spu['category']}', " | |
| 256 | - f"'{spu['shoplazza_created_at']}', '{spu['shoplazza_updated_at']}', " | |
| 257 | - f"'{spu['tenant_id']}', '{spu['creator']}', '{spu['create_time']}', " | |
| 258 | - f"'{spu['updater']}', '{spu['update_time']}', {spu['deleted']})" | |
| 273 | + f"'{escape_sql_string(spu['tags'])}', '{escape_sql_string(spu['note'])}', " | |
| 274 | + f"'{escape_sql_string(spu['category'])}', '{escape_sql_string(spu['shoplazza_created_at'])}', " | |
| 275 | + f"'{escape_sql_string(spu['shoplazza_updated_at'])}', '{escape_sql_string(spu['tenant_id'])}', " | |
| 276 | + f"'{escape_sql_string(spu['creator'])}', '{escape_sql_string(spu['create_time'])}', " | |
| 277 | + f"'{escape_sql_string(spu['updater'])}', '{escape_sql_string(spu['update_time'])}', " | |
| 278 | + f"{spu['deleted']})" | |
| 259 | 279 | ) |
| 260 | 280 | f.write(values) |
| 261 | 281 | if i < len(spus) - 1: |
| ... | ... | @@ -274,18 +294,24 @@ def generate_sql_inserts(spus: list, skus: list, output_file: str): |
| 274 | 294 | f.write(") VALUES\n") |
| 275 | 295 | |
| 276 | 296 | for i, sku in enumerate(skus): |
| 297 | + # Handle extend field (JSON, can be NULL) | |
| 298 | + extend_value = 'NULL' if sku['extend'] is None else f"'{escape_sql_string(sku['extend'])}'" | |
| 299 | + | |
| 277 | 300 | values = ( |
| 278 | - f"({sku['id']}, {sku['spu_id']}, {sku['shop_id']}, '{sku['shoplazza_id']}', " | |
| 279 | - f"'{sku['shoplazza_product_id']}', '{sku['shoplazza_image_id']}', " | |
| 280 | - f"'{sku['title']}', '{sku['sku']}', '{sku['barcode']}', {sku['position']}, " | |
| 301 | + f"({sku['id']}, {sku['spu_id']}, {sku['shop_id']}, '{escape_sql_string(sku['shoplazza_id'])}', " | |
| 302 | + f"'{escape_sql_string(sku['shoplazza_product_id'])}', '{escape_sql_string(sku['shoplazza_image_id'])}', " | |
| 303 | + f"'{escape_sql_string(sku['title'])}', '{escape_sql_string(sku['sku'])}', " | |
| 304 | + f"'{escape_sql_string(sku['barcode'])}', {sku['position']}, " | |
| 281 | 305 | f"{sku['price']}, {sku['compare_at_price']}, {sku['cost_price']}, " |
| 282 | - f"'{sku['option1']}', '{sku['option2']}', '{sku['option3']}', " | |
| 283 | - f"{sku['inventory_quantity']}, {sku['weight']}, '{sku['weight_unit']}', " | |
| 284 | - f"'{sku['image_src']}', '{sku['wholesale_price']}', '{sku['note']}', " | |
| 285 | - f"'{sku['extend']}', '{sku['shoplazza_created_at']}', " | |
| 286 | - f"'{sku['shoplazza_updated_at']}', '{sku['tenant_id']}', " | |
| 287 | - f"'{sku['creator']}', '{sku['create_time']}', '{sku['updater']}', " | |
| 288 | - f"'{sku['update_time']}', {sku['deleted']})" | |
| 306 | + f"'{escape_sql_string(sku['option1'])}', '{escape_sql_string(sku['option2'])}', " | |
| 307 | + f"'{escape_sql_string(sku['option3'])}', {sku['inventory_quantity']}, {sku['weight']}, " | |
| 308 | + f"'{escape_sql_string(sku['weight_unit'])}', '{escape_sql_string(sku['image_src'])}', " | |
| 309 | + f"'{escape_sql_string(sku['wholesale_price'])}', '{escape_sql_string(sku['note'])}', " | |
| 310 | + f"{extend_value}, '{escape_sql_string(sku['shoplazza_created_at'])}', " | |
| 311 | + f"'{escape_sql_string(sku['shoplazza_updated_at'])}', '{escape_sql_string(sku['tenant_id'])}', " | |
| 312 | + f"'{escape_sql_string(sku['creator'])}', '{escape_sql_string(sku['create_time'])}', " | |
| 313 | + f"'{escape_sql_string(sku['updater'])}', '{escape_sql_string(sku['update_time'])}', " | |
| 314 | + f"{sku['deleted']})" | |
| 289 | 315 | ) |
| 290 | 316 | f.write(values) |
| 291 | 317 | if i < len(skus) - 1: | ... | ... |
scripts/import_test_data.py
| ... | ... | @@ -24,11 +24,25 @@ def import_sql_file(db_engine, sql_file: str): |
| 24 | 24 | db_engine: SQLAlchemy database engine |
| 25 | 25 | sql_file: Path to SQL file |
| 26 | 26 | """ |
| 27 | + from sqlalchemy import text | |
| 28 | + | |
| 27 | 29 | with open(sql_file, 'r', encoding='utf-8') as f: |
| 28 | 30 | sql_content = f.read() |
| 29 | 31 | |
| 30 | 32 | # Split by semicolons to get individual statements |
| 31 | - statements = [s.strip() for s in sql_content.split(';') if s.strip() and not s.strip().startswith('--')] | |
| 33 | + # Remove comments and empty lines, then split by semicolon | |
| 34 | + lines = [] | |
| 35 | + for line in sql_content.split('\n'): | |
| 36 | + # Remove inline comments | |
| 37 | + if '--' in line: | |
| 38 | + line = line[:line.index('--')] | |
| 39 | + line = line.strip() | |
| 40 | + if line and not line.startswith('--'): | |
| 41 | + lines.append(line) | |
| 42 | + | |
| 43 | + # Join lines and split by semicolon | |
| 44 | + full_text = ' '.join(lines) | |
| 45 | + statements = [s.strip() for s in full_text.split(';') if s.strip()] | |
| 32 | 46 | |
| 33 | 47 | print(f"Executing {len(statements)} SQL statements...") |
| 34 | 48 | |
| ... | ... | @@ -36,12 +50,12 @@ def import_sql_file(db_engine, sql_file: str): |
| 36 | 50 | for i, statement in enumerate(statements, 1): |
| 37 | 51 | if statement: |
| 38 | 52 | try: |
| 39 | - conn.execute(statement) | |
| 53 | + conn.execute(text(statement)) | |
| 40 | 54 | conn.commit() |
| 41 | 55 | print(f" [{i}/{len(statements)}] Executed successfully") |
| 42 | 56 | except Exception as e: |
| 43 | 57 | print(f" [{i}/{len(statements)}] ERROR: {e}") |
| 44 | - print(f" Statement: {statement[:100]}...") | |
| 58 | + print(f" Statement: {statement[:200]}...") | |
| 45 | 59 | raise |
| 46 | 60 | |
| 47 | 61 | |
| ... | ... | @@ -109,6 +123,22 @@ def main(): |
| 109 | 123 | |
| 110 | 124 | print("Database connection successful") |
| 111 | 125 | |
| 126 | + # Clean existing data if tenant_id provided | |
| 127 | + if args.tenant_id: | |
| 128 | + print(f"\nCleaning existing data for tenant_id: {args.tenant_id}") | |
| 129 | + from sqlalchemy import text | |
| 130 | + try: | |
| 131 | + with db_engine.connect() as conn: | |
| 132 | + # Delete SKUs first (foreign key constraint) | |
| 133 | + conn.execute(text(f"DELETE FROM shoplazza_product_sku WHERE tenant_id = '{args.tenant_id}'")) | |
| 134 | + # Delete SPUs | |
| 135 | + conn.execute(text(f"DELETE FROM shoplazza_product_spu WHERE tenant_id = '{args.tenant_id}'")) | |
| 136 | + conn.commit() | |
| 137 | + print("✓ Existing data cleaned") | |
| 138 | + except Exception as e: | |
| 139 | + print(f"⚠ Warning: Failed to clean existing data: {e}") | |
| 140 | + # Continue anyway | |
| 141 | + | |
| 112 | 142 | # Import SQL file |
| 113 | 143 | print(f"\nImporting SQL file: {args.sql_file}") |
| 114 | 144 | try: | ... | ... |
scripts/ingest_shoplazza.py
| ... | ... | @@ -78,11 +78,24 @@ def main(): |
| 78 | 78 | print(f"ERROR: Failed to connect to MySQL: {e}") |
| 79 | 79 | return 1 |
| 80 | 80 | |
| 81 | - # Connect to Elasticsearch | |
| 82 | - print(f"Connecting to Elasticsearch: {args.es_host}") | |
| 83 | - es_client = ESClient(hosts=[args.es_host]) | |
| 81 | + # Connect to Elasticsearch (use unified config loading) | |
| 82 | + from config.env_config import get_es_config | |
| 83 | + es_config = get_es_config() | |
| 84 | + | |
| 85 | + # Use provided es_host or fallback to config | |
| 86 | + es_host = args.es_host or es_config.get('host', 'http://localhost:9200') | |
| 87 | + es_username = es_config.get('username') | |
| 88 | + es_password = es_config.get('password') | |
| 89 | + | |
| 90 | + print(f"Connecting to Elasticsearch: {es_host}") | |
| 91 | + if es_username and es_password: | |
| 92 | + print(f"Using authentication: {es_username}") | |
| 93 | + es_client = ESClient(hosts=[es_host], username=es_username, password=es_password) | |
| 94 | + else: | |
| 95 | + es_client = ESClient(hosts=[es_host]) | |
| 96 | + | |
| 84 | 97 | if not es_client.ping(): |
| 85 | - print(f"ERROR: Cannot connect to Elasticsearch at {args.es_host}") | |
| 98 | + print(f"ERROR: Cannot connect to Elasticsearch at {es_host}") | |
| 86 | 99 | return 1 |
| 87 | 100 | |
| 88 | 101 | # Generate and create index | ... | ... |
| ... | ... | @@ -0,0 +1,69 @@ |
| 1 | +-- SPU Test Data | |
| 2 | +INSERT INTO shoplazza_product_spu ( | |
| 3 | + id, shop_id, shoplazza_id, handle, title, brief, description, spu, | |
| 4 | + vendor, vendor_url, seo_title, seo_description, seo_keywords, | |
| 5 | + image_src, image_width, image_height, image_path, image_alt, | |
| 6 | + inventory_policy, inventory_quantity, inventory_tracking, | |
| 7 | + published, published_at, requires_shipping, taxable, | |
| 8 | + fake_sales, display_fake_sales, mixed_wholesale, need_variant_image, | |
| 9 | + has_only_default_variant, tags, note, category, | |
| 10 | + shoplazza_created_at, shoplazza_updated_at, tenant_id, | |
| 11 | + creator, create_time, updater, update_time, deleted | |
| 12 | +) VALUES | |
| 13 | +(1, 1, 'spu-1', 'product-1', '音响 海尔', '蓝牙无线音响', '<p>蓝牙无线音响,来自海尔品牌。Bluetooth wireless speaker</p>', '', '海尔', '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), | |
| 14 | +(2, 1, 'spu-2', 'product-2', '无线鼠标 华为', '人体工学无线鼠标', '<p>人体工学无线鼠标,来自华为品牌。Ergonomic wireless mouse</p>', '', '华为', '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), | |
| 15 | +(3, 1, 'spu-3', 'product-3', '显示器 Sony', '4K高清显示器', '<p>4K高清显示器,来自Sony品牌。4K high-definition monitor</p>', '', '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), | |
| 16 | +(4, 1, 'spu-4', 'product-4', '蓝牙耳机 Apple', '高品质无线蓝牙耳机', '<p>高品质无线蓝牙耳机,来自Apple品牌。High-quality wireless Bluetooth headphone</p>', '', '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), | |
| 17 | +(5, 1, 'spu-5', 'product-5', '智能手表 海尔', '多功能智能手表', '<p>多功能智能手表,来自海尔品牌。Multi-function smart watch</p>', '', '海尔', '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), | |
| 18 | +(6, 1, 'spu-6', 'product-6', '无线鼠标 Samsung', '人体工学无线鼠标', '<p>人体工学无线鼠标,来自Samsung品牌。Ergonomic wireless mouse</p>', '', '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), | |
| 19 | +(7, 1, 'spu-7', 'product-7', '显示器 美的', '4K高清显示器', '<p>4K高清显示器,来自美的品牌。4K high-definition monitor</p>', '', '美的', '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), | |
| 20 | +(8, 1, 'spu-8', 'product-8', '智能手机 华为', '高性能智能手机', '<p>高性能智能手机,来自华为品牌。High-performance smartphone</p>', '', '华为', '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), | |
| 21 | +(9, 1, 'spu-9', 'product-9', '运动鞋 Apple', '舒适透气的运动鞋', '<p>舒适透气的运动鞋,来自Apple品牌。Comfortable and breathable running shoes</p>', '', '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), | |
| 22 | +(10, 1, 'spu-10', 'product-10', '机械键盘 Sony', 'RGB背光机械键盘', '<p>RGB背光机械键盘,来自Sony品牌。RGB backlit mechanical keyboard</p>', '', '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); | |
| 23 | + | |
| 24 | +-- SKU Test Data | |
| 25 | +INSERT INTO shoplazza_product_sku ( | |
| 26 | + id, spu_id, shop_id, shoplazza_id, shoplazza_product_id, shoplazza_image_id, | |
| 27 | + title, sku, barcode, position, price, compare_at_price, cost_price, | |
| 28 | + option1, option2, option3, inventory_quantity, weight, weight_unit, | |
| 29 | + image_src, wholesale_price, note, extend, | |
| 30 | + shoplazza_created_at, shoplazza_updated_at, tenant_id, | |
| 31 | + creator, create_time, updater, update_time, deleted | |
| 32 | +) VALUES | |
| 33 | +(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), | |
| 34 | +(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), | |
| 35 | +(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), | |
| 36 | +(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), | |
| 37 | +(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), | |
| 38 | +(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), | |
| 39 | +(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), | |
| 40 | +(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), | |
| 41 | +(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), | |
| 42 | +(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), | |
| 43 | +(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), | |
| 44 | +(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), | |
| 45 | +(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), | |
| 46 | +(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), | |
| 47 | +(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), | |
| 48 | +(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), | |
| 49 | +(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), | |
| 50 | +(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), | |
| 51 | +(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), | |
| 52 | +(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), | |
| 53 | +(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), | |
| 54 | +(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), | |
| 55 | +(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), | |
| 56 | +(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), | |
| 57 | +(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), | |
| 58 | +(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), | |
| 59 | +(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), | |
| 60 | +(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), | |
| 61 | +(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), | |
| 62 | +(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), | |
| 63 | +(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), | |
| 64 | +(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), | |
| 65 | +(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), | |
| 66 | +(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), | |
| 67 | +(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), | |
| 68 | +(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), | |
| 69 | +(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); | ... | ... |
utils/db_connector.py
| ... | ... | @@ -2,7 +2,7 @@ |
| 2 | 2 | Database connector utility for MySQL connections. |
| 3 | 3 | """ |
| 4 | 4 | |
| 5 | -from sqlalchemy import create_engine | |
| 5 | +from sqlalchemy import create_engine, text | |
| 6 | 6 | from sqlalchemy.pool import QueuePool |
| 7 | 7 | from typing import Dict, Any, Optional |
| 8 | 8 | import pymysql |
| ... | ... | @@ -84,7 +84,8 @@ def test_connection(engine) -> bool: |
| 84 | 84 | """ |
| 85 | 85 | try: |
| 86 | 86 | with engine.connect() as conn: |
| 87 | - conn.execute("SELECT 1") | |
| 87 | + conn.execute(text("SELECT 1")) | |
| 88 | + conn.commit() | |
| 88 | 89 | return True |
| 89 | 90 | except Exception as e: |
| 90 | 91 | print(f"Database connection test failed: {e}") | ... | ... |