完成基本分表查询
This commit is contained in:
353
static/js/app.js
353
static/js/app.js
@@ -6,13 +6,93 @@ let filteredIdenticalResults = [];
|
||||
let currentDifferencePage = 1;
|
||||
let differencePageSize = 10;
|
||||
let filteredDifferenceResults = [];
|
||||
let isShardingMode = false; // 分表模式标志
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadDefaultConfig();
|
||||
loadConfigGroups(); // 加载配置组列表
|
||||
bindShardingEvents(); // 绑定分表相关事件
|
||||
});
|
||||
|
||||
// 绑定分表相关事件
|
||||
function bindShardingEvents() {
|
||||
// 分表模式切换事件
|
||||
const enableShardingCheckbox = document.getElementById('enableSharding');
|
||||
if (enableShardingCheckbox) {
|
||||
enableShardingCheckbox.addEventListener('change', toggleShardingMode);
|
||||
}
|
||||
|
||||
// 生产环境分表开关变化事件
|
||||
const useShardingProCheckbox = document.getElementById('use_sharding_for_pro');
|
||||
if (useShardingProCheckbox) {
|
||||
useShardingProCheckbox.addEventListener('change', function() {
|
||||
updateTableNameHints();
|
||||
});
|
||||
}
|
||||
|
||||
// 测试环境分表开关变化事件
|
||||
const useShardingTestCheckbox = document.getElementById('use_sharding_for_test');
|
||||
if (useShardingTestCheckbox) {
|
||||
useShardingTestCheckbox.addEventListener('change', function() {
|
||||
updateTableNameHints();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 切换分表模式
|
||||
function toggleShardingMode() {
|
||||
isShardingMode = document.getElementById('enableSharding').checked;
|
||||
const shardingConfig = document.getElementById('shardingConfig');
|
||||
const executeButton = document.getElementById('executeButtonText');
|
||||
const keyInputHint = document.getElementById('key_input_hint');
|
||||
const keysField = document.getElementById('keys');
|
||||
|
||||
if (isShardingMode) {
|
||||
// 启用分表模式
|
||||
shardingConfig.style.display = 'block';
|
||||
executeButton.textContent = '执行分表查询比对';
|
||||
keyInputHint.textContent = '分表模式:Key值应包含时间戳用于计算分表索引';
|
||||
keysField.placeholder = 'wmid (推荐使用包含时间戳的字段)';
|
||||
keysField.value = 'wmid';
|
||||
|
||||
// 更新查询Key输入框的占位符
|
||||
const queryValues = document.getElementById('query_values');
|
||||
queryValues.placeholder = '请输入查询的Key值,一行一个\n分表查询示例(包含时间戳):\nwmid_1609459200\nwmid_1610064000\nwmid_1610668800';
|
||||
} else {
|
||||
// 禁用分表模式
|
||||
shardingConfig.style.display = 'none';
|
||||
executeButton.textContent = '执行查询比对';
|
||||
keyInputHint.textContent = '单表模式:输入普通Key值';
|
||||
keysField.placeholder = 'docid';
|
||||
keysField.value = 'docid';
|
||||
|
||||
// 更新查询Key输入框的占位符
|
||||
const queryValues = document.getElementById('query_values');
|
||||
queryValues.placeholder = '请输入查询的Key值,一行一个\n单表查询示例:\nkey1\nkey2\nkey3';
|
||||
}
|
||||
|
||||
updateTableNameHints();
|
||||
}
|
||||
|
||||
// 更新表名字段的提示文本
|
||||
function updateTableNameHints() {
|
||||
const proTableHint = document.getElementById('pro_table_hint');
|
||||
const testTableHint = document.getElementById('test_table_hint');
|
||||
const useShardingPro = document.getElementById('use_sharding_for_pro');
|
||||
const useShardingTest = document.getElementById('use_sharding_for_test');
|
||||
|
||||
if (isShardingMode) {
|
||||
proTableHint.textContent = (useShardingPro && useShardingPro.checked) ?
|
||||
'基础表名(自动添加索引后缀)' : '完整表名';
|
||||
testTableHint.textContent = (useShardingTest && useShardingTest.checked) ?
|
||||
'基础表名(自动添加索引后缀)' : '完整表名';
|
||||
} else {
|
||||
proTableHint.textContent = '完整表名';
|
||||
testTableHint.textContent = '完整表名';
|
||||
}
|
||||
}
|
||||
|
||||
// 加载配置组列表
|
||||
async function loadConfigGroups() {
|
||||
try {
|
||||
@@ -155,6 +235,9 @@ async function saveConfigGroup() {
|
||||
|
||||
const config = getCurrentConfig();
|
||||
|
||||
// 获取分表配置
|
||||
const shardingConfig = getShardingConfig().sharding_config;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/config-groups', {
|
||||
method: 'POST',
|
||||
@@ -164,7 +247,8 @@ async function saveConfigGroup() {
|
||||
body: JSON.stringify({
|
||||
name: name,
|
||||
description: description,
|
||||
...config
|
||||
...config,
|
||||
sharding_config: shardingConfig
|
||||
})
|
||||
});
|
||||
|
||||
@@ -178,7 +262,7 @@ async function saveConfigGroup() {
|
||||
// 重新加载配置组列表
|
||||
await loadConfigGroups();
|
||||
|
||||
showAlert('success', result.message);
|
||||
showAlert('success', result.message + '(包含分表配置)');
|
||||
} else {
|
||||
showAlert('danger', result.error || '保存配置组失败');
|
||||
}
|
||||
@@ -437,13 +521,30 @@ async function executeQuery() {
|
||||
document.getElementById('loading').style.display = 'block';
|
||||
document.getElementById('results').style.display = 'none';
|
||||
|
||||
// 更新加载文本
|
||||
const loadingText = document.getElementById('loadingText');
|
||||
if (isShardingMode) {
|
||||
loadingText.textContent = '正在执行分表查询比对...';
|
||||
} else {
|
||||
loadingText.textContent = '正在执行查询比对...';
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/query', {
|
||||
let apiEndpoint = '/api/query';
|
||||
let requestConfig = config;
|
||||
|
||||
// 如果启用了分表模式,使用分表查询API和配置
|
||||
if (isShardingMode) {
|
||||
apiEndpoint = '/api/sharding-query';
|
||||
requestConfig = getShardingConfig();
|
||||
}
|
||||
|
||||
const response = await fetch(apiEndpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(config)
|
||||
body: JSON.stringify(requestConfig)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -455,6 +556,9 @@ async function executeQuery() {
|
||||
currentResults = results;
|
||||
displayResults(results);
|
||||
|
||||
// 自动刷新查询日志
|
||||
autoRefreshLogsAfterQuery();
|
||||
|
||||
} catch (error) {
|
||||
showAlert('danger', '查询失败: ' + error.message);
|
||||
} finally {
|
||||
@@ -462,11 +566,31 @@ async function executeQuery() {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取分表查询配置
|
||||
function getShardingConfig() {
|
||||
const baseConfig = getCurrentConfig();
|
||||
|
||||
return {
|
||||
...baseConfig,
|
||||
sharding_config: {
|
||||
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
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 显示查询结果
|
||||
function displayResults(results) {
|
||||
// 显示统计信息
|
||||
displayStats(results);
|
||||
|
||||
// 显示分表查询信息(如果有)
|
||||
displayShardingInfo(results);
|
||||
|
||||
// 更新选项卡计数
|
||||
document.getElementById('diff-count').textContent = results.differences.length;
|
||||
document.getElementById('identical-count').textContent = results.identical_results.length;
|
||||
@@ -487,7 +611,99 @@ function displayResults(results) {
|
||||
// 显示结果区域
|
||||
document.getElementById('results').style.display = 'block';
|
||||
|
||||
showAlert('success', `查询完成!共处理${results.total_keys}个Key,发现${results.differences.length}处差异,${results.identical_results.length}条记录完全相同`);
|
||||
// 根据查询类型显示不同的成功消息
|
||||
const queryType = isShardingMode ? '分表查询' : '单表查询';
|
||||
showAlert('success', `${queryType}完成!共处理${results.total_keys}个Key,发现${results.differences.length}处差异,${results.identical_results.length}条记录完全相同`);
|
||||
}
|
||||
|
||||
// 显示分表查询信息
|
||||
function displayShardingInfo(results) {
|
||||
const shardingInfoContainer = document.getElementById('shardingInfoContainer');
|
||||
|
||||
if (!isShardingMode || !results.sharding_info) {
|
||||
shardingInfoContainer.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
const shardingInfo = results.sharding_info;
|
||||
shardingInfoContainer.style.display = 'block';
|
||||
|
||||
let html = '<div class="row">';
|
||||
|
||||
// 生产环境分表信息
|
||||
if (shardingInfo.pro_shards) {
|
||||
html += `
|
||||
<div class="col-md-6">
|
||||
<h6 class="text-primary"><i class="fas fa-server"></i> 生产环境分表信息</h6>
|
||||
<div class="mb-3">
|
||||
<small class="text-muted">配置:${shardingInfo.pro_shards.interval_seconds}秒间隔,${shardingInfo.pro_shards.table_count}张分表</small>
|
||||
</div>
|
||||
<div class="shard-tables-info">
|
||||
`;
|
||||
|
||||
if (shardingInfo.pro_shards.queried_tables) {
|
||||
shardingInfo.pro_shards.queried_tables.forEach(table => {
|
||||
const hasError = shardingInfo.pro_shards.error_tables &&
|
||||
shardingInfo.pro_shards.error_tables.includes(table);
|
||||
const cssClass = hasError ? 'shard-error-info' : 'shard-table-info';
|
||||
html += `<span class="${cssClass}">${table}</span>`;
|
||||
});
|
||||
}
|
||||
|
||||
html += '</div></div>';
|
||||
}
|
||||
|
||||
// 测试环境分表信息
|
||||
if (shardingInfo.test_shards) {
|
||||
html += `
|
||||
<div class="col-md-6">
|
||||
<h6 class="text-success"><i class="fas fa-flask"></i> 测试环境分表信息</h6>
|
||||
<div class="mb-3">
|
||||
<small class="text-muted">配置:${shardingInfo.test_shards.interval_seconds}秒间隔,${shardingInfo.test_shards.table_count}张分表</small>
|
||||
</div>
|
||||
<div class="shard-tables-info">
|
||||
`;
|
||||
|
||||
if (shardingInfo.test_shards.queried_tables) {
|
||||
shardingInfo.test_shards.queried_tables.forEach(table => {
|
||||
const hasError = shardingInfo.test_shards.error_tables &&
|
||||
shardingInfo.test_shards.error_tables.includes(table);
|
||||
const cssClass = hasError ? 'shard-error-info' : 'shard-table-info';
|
||||
html += `<span class="${cssClass}">${table}</span>`;
|
||||
});
|
||||
}
|
||||
|
||||
html += '</div></div>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
||||
// 添加分表计算统计信息
|
||||
if (shardingInfo.calculation_stats) {
|
||||
html += `
|
||||
<div class="row mt-3">
|
||||
<div class="col-12">
|
||||
<h6><i class="fas fa-calculator"></i> 分表计算统计</h6>
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<small class="text-muted">处理Key数:<strong>${shardingInfo.calculation_stats.total_keys || 0}</strong></small>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<small class="text-muted">成功解析时间戳:<strong>${shardingInfo.calculation_stats.successful_extractions || 0}</strong></small>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<small class="text-muted">计算出分表数:<strong>${shardingInfo.calculation_stats.unique_shards || 0}</strong></small>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<small class="text-muted">解析失败:<strong class="text-danger">${shardingInfo.calculation_stats.failed_extractions || 0}</strong></small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
document.getElementById('shardingInfo').innerHTML = html;
|
||||
}
|
||||
|
||||
// 显示统计信息
|
||||
@@ -2722,4 +2938,131 @@ function showAlert(type, message) {
|
||||
alert.remove();
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// 查询日志相关功能
|
||||
let allQueryLogs = []; // 存储所有日志
|
||||
|
||||
async function refreshQueryLogs() {
|
||||
try {
|
||||
const response = await fetch('/api/query-logs');
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success && result.data) {
|
||||
allQueryLogs = result.data;
|
||||
filterLogsByLevel();
|
||||
} else {
|
||||
document.getElementById('query-logs').innerHTML = '<div class="alert alert-warning">无法获取查询日志</div>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取查询日志失败:', error);
|
||||
document.getElementById('query-logs').innerHTML = '<div class="alert alert-danger">获取查询日志失败</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function filterLogsByLevel() {
|
||||
const showInfo = document.getElementById('log-level-info').checked;
|
||||
const showWarning = document.getElementById('log-level-warning').checked;
|
||||
const showError = document.getElementById('log-level-error').checked;
|
||||
|
||||
const filteredLogs = allQueryLogs.filter(log => {
|
||||
switch(log.level) {
|
||||
case 'INFO': return showInfo;
|
||||
case 'WARNING': return showWarning;
|
||||
case 'ERROR': return showError;
|
||||
default: return true;
|
||||
}
|
||||
});
|
||||
|
||||
displayQueryLogs(filteredLogs);
|
||||
}
|
||||
|
||||
async function clearQueryLogs() {
|
||||
if (!confirm('确定要清空所有查询日志吗?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/query-logs', {
|
||||
method: 'DELETE'
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
document.getElementById('query-logs').innerHTML = '<div class="alert alert-info">查询日志已清空</div>';
|
||||
showAlert('success', '查询日志已清空');
|
||||
} else {
|
||||
showAlert('danger', '清空查询日志失败: ' + result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('清空查询日志失败:', error);
|
||||
showAlert('danger', '清空查询日志失败');
|
||||
}
|
||||
}
|
||||
|
||||
function displayQueryLogs(logs) {
|
||||
const container = document.getElementById('query-logs');
|
||||
|
||||
if (!logs || logs.length === 0) {
|
||||
container.innerHTML = '<div class="alert alert-info">暂无查询日志</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
const logHtml = logs.map(log => {
|
||||
const levelClass = {
|
||||
'INFO': 'text-primary',
|
||||
'WARNING': 'text-warning',
|
||||
'ERROR': 'text-danger',
|
||||
'DEBUG': 'text-secondary'
|
||||
}[log.level] || 'text-dark';
|
||||
|
||||
const levelIcon = {
|
||||
'INFO': 'fas fa-info-circle',
|
||||
'WARNING': 'fas fa-exclamation-triangle',
|
||||
'ERROR': 'fas fa-times-circle',
|
||||
'DEBUG': 'fas fa-bug'
|
||||
}[log.level] || 'fas fa-circle';
|
||||
|
||||
// 改进SQL高亮显示
|
||||
let message = escapeHtml(log.message);
|
||||
|
||||
// 高亮SQL查询语句
|
||||
if (message.includes('执行查询SQL:')) {
|
||||
message = message.replace(/执行查询SQL: (SELECT.*?);/g,
|
||||
'执行查询SQL: <br><code class="bg-light d-block p-2 text-dark" style="font-size: 0.9em;">$1;</code>');
|
||||
}
|
||||
|
||||
// 高亮重要信息
|
||||
message = message.replace(/(\d+\.\d{3}秒)/g, '<strong class="text-success">$1</strong>');
|
||||
message = message.replace(/(返回记录数=\d+)/g, '<strong class="text-info">$1</strong>');
|
||||
message = message.replace(/(执行时间=[\d.]+秒)/g, '<strong class="text-success">$1</strong>');
|
||||
|
||||
return `
|
||||
<div class="border-bottom py-2 log-entry" data-level="${log.level}">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div class="flex-grow-1">
|
||||
<span class="${levelClass}">
|
||||
<i class="${levelIcon}"></i>
|
||||
<strong>[${log.level}]</strong>
|
||||
</span>
|
||||
<div class="ms-4 mt-1">${message}</div>
|
||||
</div>
|
||||
<small class="text-muted ms-2 flex-shrink-0" style="min-width: 140px;">${log.timestamp}</small>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
container.innerHTML = logHtml;
|
||||
|
||||
// 自动滚动到底部
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
|
||||
// 在查询执行后自动刷新日志
|
||||
function autoRefreshLogsAfterQuery() {
|
||||
// 延迟一下确保后端日志已经记录
|
||||
setTimeout(() => {
|
||||
refreshQueryLogs();
|
||||
}, 500);
|
||||
}
|
Reference in New Issue
Block a user