完善查询

This commit is contained in:
2025-08-02 22:07:43 +08:00
parent eae6a14272
commit c3fba1b248
3 changed files with 421 additions and 7 deletions

27
app.py
View File

@@ -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']}")

View File

@@ -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 = '<div class="alert alert-info">没有原始数据可显示</div>';
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 = `
<!-- 分页控制 -->
<div class="row mb-3">
<div class="col-md-6">
<div class="d-flex align-items-center">
<label class="form-label me-2 mb-0">每页显示:</label>
<select class="form-select form-select-sm me-2" style="width: auto;" onchange="changeRawDataPageSize(this.value)">
<option value="10" ${rawDataPageSize == 10 ? 'selected' : ''}>10条</option>
<option value="20" ${rawDataPageSize == 20 ? 'selected' : ''}>20条</option>
<option value="50" ${rawDataPageSize == 50 ? 'selected' : ''}>50条</option>
<option value="100" ${rawDataPageSize == 100 ? 'selected' : ''}>100条</option>
</select>
<span class="ms-2 text-muted">共 ${filteredRawData.length} 条记录</span>
</div>
</div>
<div class="col-md-6">
<div class="d-flex justify-content-end align-items-center">
${generateRawDataPagination(currentRawDataPage, totalPages)}
</div>
</div>
</div>
`;
// 显示数据记录
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 += `
<div class="card mb-3 border-${item.environment === 'production' ? 'primary' : 'success'}">
<div class="card-header bg-light">
<div class="row align-items-center">
<div class="col">
<strong>记录 #${globalIndex}</strong>
<span class="badge ${envBadgeClass} ms-2">${item.displayName}</span>
</div>
<div class="col-auto">
<button class="btn btn-sm btn-outline-secondary me-2" type="button"
data-bs-toggle="collapse" data-bs-target="#rawCollapse${globalIndex}">
<i class="fas fa-eye"></i> 查看详情
</button>
<button class="btn btn-sm btn-outline-info" onclick='copyRawRecord(${JSON.stringify(JSON.stringify(item.data))})'>
<i class="fas fa-copy"></i> 复制
</button>
</div>
</div>
<p class="mb-0 mt-2"><strong>主键:</strong> ${keyValue}</p>
</div>
<div class="collapse" id="rawCollapse${globalIndex}">
<div class="card-body">
<pre class="bg-light p-3 rounded" style="max-height: 500px; overflow-y: auto; font-size: 0.9em; line-height: 1.4;">${escapeHtml(jsonData)}</pre>
</div>
</div>
</div>
`;
});
// 底部分页
if (totalPages > 1) {
html += `
<div class="row mt-3">
<div class="col-12">
<div class="d-flex justify-content-center">
${generateRawDataPagination(currentRawDataPage, totalPages)}
</div>
</div>
</div>
`;
}
container.innerHTML = html;
}
// 生成原始数据分页导航
function generateRawDataPagination(currentPage, totalPages) {
if (totalPages <= 1) return '';
let html = '<nav><ul class="pagination pagination-sm mb-0">';
// 上一页
html += `
<li class="page-item ${currentPage === 1 ? 'disabled' : ''}">
<a class="page-link" href="#" onclick="goToRawDataPage(${currentPage - 1})">
<i class="fas fa-chevron-left"></i>
</a>
</li>
`;
// 页码
const startPage = Math.max(1, currentPage - 2);
const endPage = Math.min(totalPages, currentPage + 2);
if (startPage > 1) {
html += '<li class="page-item"><a class="page-link" href="#" onclick="goToRawDataPage(1)">1</a></li>';
if (startPage > 2) {
html += '<li class="page-item disabled"><span class="page-link">...</span></li>';
}
}
for (let i = startPage; i <= endPage; i++) {
html += `
<li class="page-item ${i === currentPage ? 'active' : ''}">
<a class="page-link" href="#" onclick="goToRawDataPage(${i})">${i}</a>
</li>
`;
}
if (endPage < totalPages) {
if (endPage < totalPages - 1) {
html += '<li class="page-item disabled"><span class="page-link">...</span></li>';
}
html += `<li class="page-item"><a class="page-link" href="#" onclick="goToRawDataPage(${totalPages})">${totalPages}</a></li>`;
}
// 下一页
html += `
<li class="page-item ${currentPage === totalPages ? 'disabled' : ''}">
<a class="page-link" href="#" onclick="goToRawDataPage(${currentPage + 1})">
<i class="fas fa-chevron-right"></i>
</a>
</li>
`;
html += '</ul></nav>';
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);
}
}

View File

@@ -605,6 +605,11 @@
<i class="fas fa-check-circle"></i> 相同结果 <span class="badge bg-success ms-1" id="identical-count">0</span>
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="rawdata-tab" data-bs-toggle="tab" data-bs-target="#rawdata-panel" type="button" role="tab">
<i class="fas fa-database"></i> 原始数据 <span class="badge bg-info ms-1" id="rawdata-count">0</span>
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="summary-tab" data-bs-toggle="tab" data-bs-target="#summary-panel" type="button" role="tab">
<i class="fas fa-chart-pie"></i> 比较总结
@@ -639,6 +644,40 @@
</div>
</div>
<!-- 原始数据面板 -->
<div class="tab-pane fade" id="rawdata-panel" role="tabpanel">
<div class="container-fluid">
<!-- 筛选控制区 -->
<div class="row mb-3">
<div class="col-md-6">
<div class="d-flex align-items-center">
<label class="form-label me-2 mb-0">显示环境:</label>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="showProData" checked onchange="filterRawData()">
<label class="form-check-label" for="showProData">生产环境</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="showTestData" checked onchange="filterRawData()">
<label class="form-check-label" for="showTestData">测试环境</label>
</div>
</div>
</div>
<div class="col-md-6">
<div class="input-group">
<span class="input-group-text"><i class="fas fa-search"></i></span>
<input type="text" class="form-control" placeholder="搜索Key或字段值..."
onkeyup="searchRawData(this.value)" id="rawDataSearch">
</div>
</div>
</div>
<!-- 原始数据内容 -->
<div id="raw-data-content">
<!-- 原始数据将在这里动态生成 -->
</div>
</div>
</div>
<!-- 比较总结面板 -->
<div class="tab-pane fade" id="summary-panel" role="tabpanel">
<div id="comparison-summary">