From c3fba1b2485833d6a1b0ca422fa7abe170cb8a55 Mon Sep 17 00:00:00 2001 From: YoVinchen Date: Sat, 2 Aug 2025 22:07:43 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 27 ++- static/js/app.js | 362 ++++++++++++++++++++++++++++++++++++++ templates/db_compare.html | 39 ++++ 3 files changed, 421 insertions(+), 7 deletions(-) diff --git a/app.py b/app.py index 3d644a6..e453ffa 100644 --- a/app.py +++ b/app.py @@ -906,9 +906,11 @@ def execute_mixed_query(pro_session, test_session, pro_config, test_config, keys # 处理生产环境查询 if sharding_config.get('use_sharding_for_pro', False): + # 获取生产环境分表配置参数,优先使用专用参数,否则使用通用参数 + pro_interval = sharding_config.get('pro_interval_seconds') or sharding_config.get('interval_seconds', 604800) + pro_table_count = sharding_config.get('pro_table_count') or sharding_config.get('table_count', 14) + # 记录生产环境分表配置信息 - pro_interval = sharding_config.get('pro_interval_seconds', 604800) - pro_table_count = sharding_config.get('pro_table_count', 14) logger.info(f"=== 生产环境分表配置 ===") logger.info(f"启用分表查询: True") logger.info(f"时间间隔: {pro_interval}秒 ({pro_interval//86400}天)") @@ -954,9 +956,11 @@ def execute_mixed_query(pro_session, test_session, pro_config, test_config, keys # 处理测试环境查询 if sharding_config.get('use_sharding_for_test', False): + # 获取测试环境分表配置参数,优先使用专用参数,否则使用通用参数 + test_interval = sharding_config.get('test_interval_seconds') or sharding_config.get('interval_seconds', 604800) + test_table_count = sharding_config.get('test_table_count') or sharding_config.get('table_count', 14) + # 记录测试环境分表配置信息 - test_interval = sharding_config.get('test_interval_seconds', 604800) - test_table_count = sharding_config.get('test_table_count', 14) logger.info(f"=== 测试环境分表配置 ===") logger.info(f"启用分表查询: True") logger.info(f"时间间隔: {test_interval}秒 ({test_interval//86400}天)") @@ -980,8 +984,8 @@ def execute_mixed_query(pro_session, test_session, pro_config, test_config, keys results['test_data'] = test_data results['sharding_info']['test_shards'] = { 'enabled': True, - 'interval_seconds': sharding_config.get('test_interval_seconds', 604800), - 'table_count': sharding_config.get('test_table_count', 14), + 'interval_seconds': test_interval, + 'table_count': test_table_count, 'queried_tables': test_queried_tables, 'error_tables': test_error_tables, 'failed_keys': test_failed_keys @@ -1214,7 +1218,16 @@ def sharding_query_compare(): logger.info(f" values数量: {len(values)}") logger.info(f" fields_to_compare: {fields_to_compare}") logger.info(f" exclude_fields: {exclude_fields}") - logger.info(f" sharding_config: {sharding_config}") + logger.info(f" sharding_config原始数据: {sharding_config}") + logger.info(f" sharding_config具体参数:") + logger.info(f" use_sharding_for_pro: {sharding_config.get('use_sharding_for_pro')}") + logger.info(f" use_sharding_for_test: {sharding_config.get('use_sharding_for_test')}") + logger.info(f" pro_interval_seconds: {sharding_config.get('pro_interval_seconds')}") + logger.info(f" pro_table_count: {sharding_config.get('pro_table_count')}") + logger.info(f" test_interval_seconds: {sharding_config.get('test_interval_seconds')}") + logger.info(f" test_table_count: {sharding_config.get('test_table_count')}") + logger.info(f" interval_seconds: {sharding_config.get('interval_seconds')}") + logger.info(f" table_count: {sharding_config.get('table_count')}") logger.info(f"分表查询配置:{len(values)}个key值,生产表:{pro_config['table']},测试表:{test_config['table']}") diff --git a/static/js/app.js b/static/js/app.js index bbe0014..3d10ba7 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -718,6 +718,13 @@ function getShardingConfig() { enabled: document.getElementById('enableSharding').checked, use_sharding_for_pro: document.getElementById('use_sharding_for_pro').checked, use_sharding_for_test: document.getElementById('use_sharding_for_test').checked, + // 生产环境分表参数 + pro_interval_seconds: parseInt(document.getElementById('pro_interval_seconds').value) || 604800, + pro_table_count: parseInt(document.getElementById('pro_table_count').value) || 14, + // 测试环境分表参数 + test_interval_seconds: parseInt(document.getElementById('test_interval_seconds').value) || 604800, + test_table_count: parseInt(document.getElementById('test_table_count').value) || 14, + // 保持向后兼容的通用参数 interval_seconds: parseInt(document.getElementById('pro_interval_seconds').value) || 604800, table_count: parseInt(document.getElementById('pro_table_count').value) || 14 } @@ -736,6 +743,11 @@ function displayResults(results) { document.getElementById('diff-count').textContent = results.differences.length; document.getElementById('identical-count').textContent = results.identical_results.length; + // 计算原始数据总数 + const totalRawData = (results.raw_pro_data ? results.raw_pro_data.length : 0) + + (results.raw_test_data ? results.raw_test_data.length : 0); + document.getElementById('rawdata-count').textContent = totalRawData; + // 初始化相同结果分页数据 filteredIdenticalResults = results.identical_results; currentIdenticalPage = 1; @@ -747,6 +759,7 @@ function displayResults(results) { // 显示各个面板内容 displayDifferences(); displayIdenticalResults(); + displayRawData(results); displayComparisonSummary(results.summary); // 显示结果区域 @@ -3202,4 +3215,353 @@ function autoRefreshLogsAfterQuery() { setTimeout(() => { refreshQueryLogs(); }, 500); +} + +// 原始数据相关变量 +let currentRawData = null; +let filteredRawData = []; +let currentRawDataPage = 1; +let rawDataPageSize = 20; + +// 显示原始数据 +function displayRawData(results) { + currentRawData = results; + + // 添加调试信息 + console.log('displayRawData - results.query_config:', results.query_config); + console.log('displayRawData - 当前数据键:', Object.keys(results)); + + // 合并生产和测试环境数据 + const combinedData = []; + + if (results.raw_pro_data) { + results.raw_pro_data.forEach(record => { + combinedData.push({ + environment: 'production', + data: record, + displayName: '生产环境' + }); + }); + } + + if (results.raw_test_data) { + results.raw_test_data.forEach(record => { + combinedData.push({ + environment: 'test', + data: record, + displayName: '测试环境' + }); + }); + } + + // 按环境排序,生产环境在前 + combinedData.sort((a, b) => { + if (a.environment === 'production' && b.environment === 'test') return -1; + if (a.environment === 'test' && b.environment === 'production') return 1; + return 0; + }); + + filteredRawData = combinedData; + currentRawDataPage = 1; + renderRawDataContent(); +} + +// 渲染原始数据内容 +function renderRawDataContent() { + const container = document.getElementById('raw-data-content'); + + if (!filteredRawData.length) { + container.innerHTML = '
没有原始数据可显示
'; + return; + } + + // 分页计算 + const totalPages = Math.ceil(filteredRawData.length / rawDataPageSize); + const startIndex = (currentRawDataPage - 1) * rawDataPageSize; + const endIndex = startIndex + rawDataPageSize; + const currentPageData = filteredRawData.slice(startIndex, endIndex); + + let html = ` + +
+
+
+ + + 共 ${filteredRawData.length} 条记录 +
+
+
+
+ ${generateRawDataPagination(currentRawDataPage, totalPages)} +
+
+
+ `; + + // 显示数据记录 + currentPageData.forEach((item, index) => { + const globalIndex = startIndex + index + 1; + const envBadgeClass = item.environment === 'production' ? 'bg-primary' : 'bg-success'; + + // 获取主键值用于标识 - 修复主键解析问题 + let keyValue = 'N/A'; + + // 从当前配置获取keys,因为query_config可能不在results中 + const currentConfig = getCurrentConfig(); + console.log('渲染原始数据 - currentConfig.query_config.keys:', currentConfig.query_config.keys); + console.log('渲染原始数据 - item.data keys:', Object.keys(item.data)); + + if (currentConfig && currentConfig.query_config && currentConfig.query_config.keys && currentConfig.query_config.keys.length > 0) { + const keyFields = currentConfig.query_config.keys; + const keyValues = keyFields.map(key => { + const value = item.data[key]; + console.log(`查找主键字段 ${key}: `, value); + return value || 'N/A'; + }).filter(val => val !== 'N/A'); + + if (keyValues.length > 0) { + keyValue = keyValues.join(', '); + } + } else { + // 如果没有配置主键,尝试常见的主键字段名 + const commonKeyFields = ['id', 'statusid', 'key', 'uuid']; + for (const field of commonKeyFields) { + if (item.data[field] !== undefined) { + keyValue = item.data[field]; + break; + } + } + } + + // 格式化JSON数据 + const jsonData = JSON.stringify(item.data, null, 2); + + html += ` +
+
+
+
+ 记录 #${globalIndex} + ${item.displayName} +
+
+ + +
+
+

主键: ${keyValue}

+
+
+
+
${escapeHtml(jsonData)}
+
+
+
+ `; + }); + + // 底部分页 + if (totalPages > 1) { + html += ` +
+
+
+ ${generateRawDataPagination(currentRawDataPage, totalPages)} +
+
+
+ `; + } + + container.innerHTML = html; +} + +// 生成原始数据分页导航 +function generateRawDataPagination(currentPage, totalPages) { + if (totalPages <= 1) return ''; + + let html = ''; + return html; +} + +// 跳转到指定原始数据页面 +function goToRawDataPage(page) { + const totalPages = Math.ceil(filteredRawData.length / rawDataPageSize); + if (page < 1 || page > totalPages) return; + + currentRawDataPage = page; + renderRawDataContent(); +} + +// 改变原始数据每页显示数量 +function changeRawDataPageSize(newSize) { + rawDataPageSize = parseInt(newSize); + currentRawDataPage = 1; + renderRawDataContent(); +} + +// 过滤原始数据(按环境) +function filterRawData() { + if (!currentRawData) return; + + const showPro = document.getElementById('showProData').checked; + const showTest = document.getElementById('showTestData').checked; + + let combinedData = []; + + if (showPro && currentRawData.raw_pro_data) { + currentRawData.raw_pro_data.forEach(record => { + combinedData.push({ + environment: 'production', + data: record, + displayName: '生产环境' + }); + }); + } + + if (showTest && currentRawData.raw_test_data) { + currentRawData.raw_test_data.forEach(record => { + combinedData.push({ + environment: 'test', + data: record, + displayName: '测试环境' + }); + }); + } + + // 按环境排序 + combinedData.sort((a, b) => { + if (a.environment === 'production' && b.environment === 'test') return -1; + if (a.environment === 'test' && b.environment === 'production') return 1; + return 0; + }); + + filteredRawData = combinedData; + currentRawDataPage = 1; + renderRawDataContent(); +} + +// 搜索原始数据 +function searchRawData(searchTerm) { + if (!currentRawData) return; + + const showPro = document.getElementById('showProData').checked; + const showTest = document.getElementById('showTestData').checked; + + let combinedData = []; + + if (showPro && currentRawData.raw_pro_data) { + currentRawData.raw_pro_data.forEach(record => { + combinedData.push({ + environment: 'production', + data: record, + displayName: '生产环境' + }); + }); + } + + if (showTest && currentRawData.raw_test_data) { + currentRawData.raw_test_data.forEach(record => { + combinedData.push({ + environment: 'test', + data: record, + displayName: '测试环境' + }); + }); + } + + if (searchTerm.trim()) { + const term = searchTerm.toLowerCase(); + combinedData = combinedData.filter(item => { + // 搜索所有字段值 + return Object.values(item.data).some(value => + String(value).toLowerCase().includes(term) + ); + }); + } + + // 按环境排序 + combinedData.sort((a, b) => { + if (a.environment === 'production' && b.environment === 'test') return -1; + if (a.environment === 'test' && b.environment === 'production') return 1; + return 0; + }); + + filteredRawData = combinedData; + currentRawDataPage = 1; + renderRawDataContent(); +} + +// 复制原始记录 +function copyRawRecord(recordStr) { + try { + const record = JSON.parse(recordStr); + const formattedData = JSON.stringify(record, null, 2); + + navigator.clipboard.writeText(formattedData).then(() => { + showAlert('success', '原始记录已复制到剪贴板'); + }).catch(err => { + console.error('复制失败:', err); + showAlert('danger', '复制失败,请手动选择复制'); + }); + } catch (error) { + showAlert('danger', '复制失败: ' + error.message); + } } \ No newline at end of file diff --git a/templates/db_compare.html b/templates/db_compare.html index e749eb1..fe6fa33 100644 --- a/templates/db_compare.html +++ b/templates/db_compare.html @@ -605,6 +605,11 @@ 相同结果 0 +