修复reids

This commit is contained in:
2025-08-04 21:55:35 +08:00
parent dbf4255aea
commit 07467d27ae
4 changed files with 1275 additions and 617 deletions

4
app.py
View File

@@ -52,6 +52,6 @@ if __name__ == '__main__':
logger.info("=== BigDataTool 启动 ===")
logger.info("应用架构:模块化")
logger.info("支持功能:单表查询、分表查询、多主键查询、配置管理、查询历史")
logger.info("访问地址http://localhost:5001")
logger.info("访问地址http://localhost:5000")
logger.info("API文档/api/* 路径下的所有端点")
app.run(debug=True, port=5001)
app.run(debug=True, port=5000)

View File

@@ -51,6 +51,10 @@ def setup_routes(app, query_log_collector):
def redis_compare():
return render_template('redis_compare.html')
@app.route('/redis-js-test')
def redis_js_test():
return render_template('redis_js_test.html')
@app.route('/redis-test')
def redis_test():
return render_template('redis_test.html')
@@ -783,6 +787,62 @@ def setup_routes(app, query_log_collector):
logger.info(f"Redis比较完成")
logger.info(f"比较统计: 总计{result['stats']['total_keys']}个key相同{result['stats']['identical_count']}个,不同{result['stats']['different_count']}")
# 增强结果,添加原生数据信息
enhanced_result = result.copy()
enhanced_result['raw_data'] = {
'cluster1_data': [],
'cluster2_data': []
}
# 从比较结果中提取原生数据信息
for item in result.get('identical_results', []):
if 'key' in item and 'value' in item:
enhanced_result['raw_data']['cluster1_data'].append({
'key': item['key'],
'value': item['value'],
'type': 'identical'
})
enhanced_result['raw_data']['cluster2_data'].append({
'key': item['key'],
'value': item['value'],
'type': 'identical'
})
for item in result.get('different_results', []):
if 'key' in item:
if 'cluster1_value' in item:
enhanced_result['raw_data']['cluster1_data'].append({
'key': item['key'],
'value': item['cluster1_value'],
'type': 'different'
})
if 'cluster2_value' in item:
enhanced_result['raw_data']['cluster2_data'].append({
'key': item['key'],
'value': item['cluster2_value'],
'type': 'different'
})
for item in result.get('missing_results', []):
if 'key' in item:
if 'cluster1_value' in item and item['cluster1_value'] is not None:
enhanced_result['raw_data']['cluster1_data'].append({
'key': item['key'],
'value': item['cluster1_value'],
'type': 'missing'
})
if 'cluster2_value' in item and item['cluster2_value'] is not None:
enhanced_result['raw_data']['cluster2_data'].append({
'key': item['key'],
'value': item['cluster2_value'],
'type': 'missing'
})
logger.info(f"原生数据统计: 集群1={len(enhanced_result['raw_data']['cluster1_data'])}条, 集群2={len(enhanced_result['raw_data']['cluster2_data'])}")
# 使用增强结果进行后续处理
result = enhanced_result
# 自动保存Redis查询历史记录
try:
# 生成历史记录名称
@@ -790,29 +850,42 @@ def setup_routes(app, query_log_collector):
history_name = f"Redis比较_{timestamp}"
history_description = f"自动保存 - Redis比较{result['stats']['total_keys']}个Key发现{result['stats']['different_count']}处差异"
# 保存历史记录
history_id = save_query_history(
# 计算查询键值列表
query_keys = []
if query_options.get('mode') == 'specified':
query_keys = query_options.get('keys', [])
elif query_options.get('mode') == 'random':
# 对于随机模式,从结果中提取实际查询的键
for item in result.get('identical_results', []):
if 'key' in item:
query_keys.append(item['key'])
for item in result.get('different_results', []):
if 'key' in item:
query_keys.append(item['key'])
for item in result.get('missing_results', []):
if 'key' in item:
query_keys.append(item['key'])
# 保存Redis查询历史记录
history_id = save_redis_query_history(
name=history_name,
description=history_description,
pro_config=cluster1_config,
test_config=cluster2_config,
query_config=query_options,
query_keys=result.get('query_options', {}).get('keys', []),
cluster1_config=cluster1_config,
cluster2_config=cluster2_config,
query_options=query_options,
query_keys=query_keys,
results_summary=result['stats'],
execution_time=result['performance_report']['total_time'],
total_keys=result['stats']['total_keys'],
differences_count=result['stats']['different_count'],
different_count=result['stats']['different_count'],
identical_count=result['stats']['identical_count'],
query_type='redis',
# 添加查询结果数据
missing_count=result['stats']['missing_in_cluster1'] + result['stats']['missing_in_cluster2'] + result['stats']['both_missing'],
raw_results={
'identical_results': result['identical_results'],
'different_results': result['different_results'],
'missing_results': result['missing_results'],
'performance_report': result['performance_report']
},
differences_data=result['different_results'],
identical_data=result['identical_results']
}
)
# 关联查询日志与历史记录
@@ -824,6 +897,8 @@ def setup_routes(app, query_log_collector):
except Exception as e:
logger.warning(f"保存Redis查询历史记录失败: {e}")
import traceback
logger.error(f"详细错误信息: {traceback.format_exc()}")
# 结束查询批次
query_log_collector.end_current_batch()
@@ -1022,3 +1097,43 @@ def setup_routes(app, query_log_collector):
return jsonify({'success': True, 'message': 'Redis查询历史记录删除成功'})
else:
return jsonify({'success': False, 'error': 'Redis查询历史记录删除失败'}), 500
# Redis查询日志API
@app.route('/api/redis/query-logs', methods=['GET'])
def api_get_redis_query_logs():
"""获取Redis查询日志"""
try:
limit = request.args.get('limit', 100, type=int)
# 获取最新的查询日志
logs = query_log_collector.get_logs(limit=limit)
# 过滤Redis相关的日志
redis_logs = []
for log in logs:
if (log.get('message') and 'redis' in log.get('message', '').lower()) or log.get('query_type') == 'redis':
redis_logs.append(log)
return jsonify({'success': True, 'data': redis_logs})
except Exception as e:
logger.error(f"获取Redis查询日志失败: {e}")
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/redis/query-logs/history/<int:history_id>', methods=['GET'])
def api_get_redis_query_logs_by_history(history_id):
"""获取特定历史记录的Redis查询日志"""
try:
logs = query_log_collector.get_logs_by_history_id(history_id)
return jsonify({'success': True, 'data': logs})
except Exception as e:
logger.error(f"获取历史记录 {history_id} 的Redis查询日志失败: {e}")
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/redis/query-logs', methods=['DELETE'])
def api_clear_redis_query_logs():
"""清空Redis查询日志"""
try:
query_log_collector.clear_logs()
return jsonify({'success': True, 'message': 'Redis查询日志清空成功'})
except Exception as e:
logger.error(f"清空Redis查询日志失败: {e}")
return jsonify({'success': False, 'error': str(e)}), 500

File diff suppressed because it is too large Load Diff

View File

@@ -67,11 +67,28 @@
.node-input input {
margin-right: 10px;
}
.performance-section {
background: linear-gradient(135deg, #e3f2fd 0%, #f3e5f5 100%);
.btn-redis {
background: linear-gradient(135deg, #dc143c 0%, #b91c1c 100%);
border: none;
color: white;
}
.btn-redis:hover {
background: linear-gradient(135deg, #b91c1c 0%, #991b1b 100%);
color: white;
transform: translateY(-1px);
}
.redis-logo {
color: #dc143c;
}
.cluster-config {
border: 2px solid #e9ecef;
border-radius: 10px;
padding: 20px;
margin-top: 20px;
padding: 15px;
margin-bottom: 15px;
}
.cluster-config.active {
border-color: #dc143c;
background-color: #fff8f8;
}
.log-viewer {
background-color: #1e1e1e;
@@ -94,28 +111,22 @@
color: #007bff;
text-decoration: none;
}
.btn-redis {
background: linear-gradient(135deg, #dc143c 0%, #b91c1c 100%);
border: none;
color: white;
.pagination {
--bs-pagination-padding-x: 0.5rem;
--bs-pagination-padding-y: 0.25rem;
--bs-pagination-font-size: 0.875rem;
}
.btn-redis:hover {
background: linear-gradient(135deg, #b91c1c 0%, #991b1b 100%);
color: white;
transform: translateY(-1px);
}
.redis-logo {
color: #dc143c;
}
.cluster-config {
border: 2px solid #e9ecef;
border-radius: 10px;
padding: 20px;
margin-bottom: 15px;
}
.cluster-config.active {
border-color: #dc143c;
background-color: #fff8f8;
/* 确保提示消息在最顶层 */
.alert {
position: fixed !important;
top: 20px !important;
left: 50% !important;
transform: translateX(-50%) !important;
z-index: 9999 !important;
min-width: 300px !important;
max-width: 600px !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3) !important;
}
</style>
</head>
@@ -141,7 +152,7 @@
</div>
</nav>
<div class="container mt-4">
<div class="container-fluid mt-4">
<!-- 面包屑导航 -->
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
@@ -151,31 +162,25 @@
</nav>
<!-- 页面标题 -->
<div class="row mb-4">
<div class="row">
<div class="col-12">
<div class="d-flex align-items-center">
<i class="fab fa-redis redis-logo fs-1 me-3"></i>
<div>
<h1 class="mb-1">Redis集群比对工具</h1>
<p class="text-muted mb-0">专业的Redis集群数据比对工具支持随机采样和指定Key查询</p>
</div>
</div>
<h1 class="text-center mb-4">
<i class="fab fa-redis redis-logo"></i> Redis集群比对工具
<small class="text-muted d-block fs-6 mt-2">专业的Redis集群数据比对工具支持随机采样和指定Key查询</small>
</h1>
</div>
</div>
<!-- 配置区域 -->
<div class="row">
<!-- 配置管理 -->
<div class="col-lg-12">
<div class="config-section mb-4">
<h4><i class="fas fa-cogs me-2"></i>配置管理</h4>
<!-- 配置面板 -->
<div class="col-lg-4">
<div class="config-section">
<h4><i class="fas fa-cogs"></i> 配置管理</h4>
<div class="row">
<!-- 配置组管理 -->
<div class="col-md-6">
<div class="card">
<div class="card mb-3">
<div class="card-header">
<h6><i class="fas fa-layer-group me-2"></i>配置组管理</h6>
<h6><i class="fas fa-layer-group"></i> 配置组管理</h6>
</div>
<div class="card-body">
<div class="row mb-3">
@@ -191,196 +196,130 @@
</div>
</div>
<div class="row">
<div class="col-6">
<div class="col-4">
<button class="btn btn-success btn-sm w-100" onclick="showSaveRedisConfigDialog()">
<i class="fas fa-save"></i> 保存配置
<i class="fas fa-save"></i> 保存配置
</button>
</div>
<div class="col-6">
<div class="col-4">
<button class="btn btn-info btn-sm w-100" onclick="showManageRedisConfigDialog()">
<i class="fas fa-cog"></i> 管理配置
<i class="fas fa-cog"></i> 管理配置
</button>
</div>
</div>
</div>
<div class="col-4">
<button class="btn btn-warning btn-sm w-100" onclick="showImportRedisConfigDialog()">
<i class="fas fa-file-import"></i> 导入配置
</button>
</div>
</div>
<!-- 查询历史 -->
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h6><i class="fas fa-history me-2"></i>查询历史</h6>
<!-- 查询历史和日志 -->
<div class="row mt-3">
<div class="col-12">
<h6><i class="fas fa-history"></i> 历史记录与日志</h6>
</div>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-8">
<select class="form-select form-select-sm" id="redisHistorySelect">
<option value="">选择历史记录...</option>
<option value="">选择查询历史...</option>
</select>
</div>
<div class="col-4">
<button class="btn btn-warning btn-sm w-100" onclick="loadSelectedRedisHistory()">
<i class="fas fa-history"></i> 加载
<i class="fas fa-download"></i> 加载
</button>
</div>
</div>
<div class="row">
<div class="col-6">
<button class="btn btn-secondary btn-sm w-100" onclick="showSaveRedisHistoryDialog()">
<i class="fas fa-bookmark"></i> 保存历史
<button class="btn btn-info btn-sm w-100" onclick="showManageRedisHistoryDialog()">
<i class="fas fa-history"></i> 管理历史
</button>
</div>
<div class="col-6">
<button class="btn btn-info btn-sm w-100" onclick="showManageRedisHistoryDialog()">
<i class="fas fa-list"></i> 管理历史
<button class="btn btn-outline-info btn-sm w-100" onclick="showRedisQueryLogsDialog()">
<i class="fas fa-file-alt"></i> 查询日志
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 一键导入区域 -->
<div class="row mt-3">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h6><i class="fas fa-download me-2"></i>配置导入</h6>
</div>
<div class="card-body">
<button class="btn btn-outline-primary btn-sm me-2" onclick="showImportRedisConfigDialog('cluster1')">
<i class="fas fa-download"></i> 导入集群1配置
</button>
<button class="btn btn-outline-primary btn-sm" onclick="showImportRedisConfigDialog('cluster2')">
<i class="fas fa-download"></i> 导入集群2配置
</button>
<small class="form-text text-muted d-block mt-2">
支持YAML格式配置导入clusterName、clusterAddress、clusterPassword等
</small>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h6><i class="fas fa-file-alt me-2"></i>查询日志</h6>
</div>
<div class="card-body">
<button class="btn btn-outline-info btn-sm" onclick="showRedisQueryLogsDialog()">
<i class="fas fa-eye"></i> 查看查询日志
</button>
<small class="form-text text-muted d-block mt-2">
查看Redis比较操作的详细执行日志
</small>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Redis集群配置 -->
<div class="col-lg-12">
<div class="config-section">
<h4><i class="fas fa-server me-2"></i>Redis集群配置</h4>
<div class="row">
<!-- 第一个集群配置 -->
<div class="col-md-6">
<div class="cluster-config" id="cluster1-config">
<h5><i class="fas fa-database me-2"></i>集群1 (生产环境)</h5>
<div class="mb-3">
<label class="form-label">集群名称</label>
<input type="text" class="form-control" id="cluster1-name" value="生产集群" placeholder="输入集群名称">
<div class="card mb-3">
<div class="card-header">
<h6><i class="fas fa-server"></i> Redis集群配置</h6>
</div>
<div class="mb-3">
<label class="form-label">节点列表</label>
<div id="cluster1-nodes">
<div class="card-body">
<!-- 集群1配置 -->
<div class="cluster-config mb-3">
<h6 class="text-primary"><i class="fas fa-server"></i> 集群1 (生产)</h6>
<div class="row mb-2">
<div class="col-12">
<label class="form-label">集群名称</label>
<input type="text" class="form-control form-control-sm" id="cluster1Name" placeholder="生产集群" value="生产集群">
</div>
</div>
<div class="mb-2">
<label class="form-label">Redis节点</label>
<div id="cluster1Nodes">
<div class="node-input">
<input type="text" class="form-control node-host" placeholder="主机地址" value="127.0.0.1">
<input type="number" class="form-control node-port" placeholder="端口" value="7000" style="width: 100px;">
<button type="button" class="btn btn-outline-danger btn-sm remove-node">
<input type="text" class="form-control form-control-sm me-2" placeholder="127.0.0.1" value="127.0.0.1" style="flex: 2;">
<input type="number" class="form-control form-control-sm me-2" placeholder="6379" value="6379" style="flex: 1;">
<button type="button" class="btn btn-outline-danger btn-sm" onclick="removeRedisNode(this, 'cluster1')">
<i class="fas fa-minus"></i>
</button>
</div>
</div>
<button type="button" class="btn btn-outline-primary btn-sm mt-2" onclick="addNode('cluster1')">
<button type="button" class="btn btn-outline-primary btn-sm mt-2" onclick="addRedisNode('cluster1')">
<i class="fas fa-plus"></i> 添加节点
</button>
</div>
<div class="mb-3">
<label class="form-label">密码 (可选)</label>
<input type="password" class="form-control" id="cluster1-password" placeholder="Redis密码">
</div>
<div class="row">
<div class="col-6">
<label class="form-label">连接超时 (秒)</label>
<input type="number" class="form-control" id="cluster1-timeout" value="3" min="1" max="30">
<label class="form-label">密码</label>
<input type="password" class="form-control form-control-sm" id="cluster1Password" placeholder="可选">
</div>
<div class="col-6">
<label class="form-label">最大连接数</label>
<input type="number" class="form-control" id="cluster1-max-conn" value="16" min="1" max="100">
</div>
</div>
<div class="mt-3">
<button type="button" class="btn btn-outline-success btn-sm" onclick="testConnection('cluster1')">
<button class="btn btn-outline-primary btn-sm mt-4 w-100" onclick="testConnection('cluster1')">
<i class="fas fa-plug"></i> 测试连接
</button>
</div>
</div>
</div>
<!-- 第二个集群配置 -->
<div class="col-md-6">
<div class="cluster-config" id="cluster2-config">
<h5><i class="fas fa-database me-2"></i>集群2 (测试环境)</h5>
<div class="mb-3">
<!-- 集群2配置 -->
<div class="cluster-config">
<h6 class="text-success"><i class="fas fa-server"></i> 集群2 (测试)</h6>
<div class="row mb-2">
<div class="col-12">
<label class="form-label">集群名称</label>
<input type="text" class="form-control" id="cluster2-name" value="测试集群" placeholder="输入集群名称">
<input type="text" class="form-control form-control-sm" id="cluster2Name" placeholder="测试集群" value="测试集群">
</div>
<div class="mb-3">
<label class="form-label">节点列表</label>
<div id="cluster2-nodes">
</div>
<div class="mb-2">
<label class="form-label">Redis节点</label>
<div id="cluster2Nodes">
<div class="node-input">
<input type="text" class="form-control node-host" placeholder="主机地址" value="127.0.0.1">
<input type="number" class="form-control node-port" placeholder="端口" value="7001" style="width: 100px;">
<button type="button" class="btn btn-outline-danger btn-sm remove-node">
<input type="text" class="form-control form-control-sm me-2" placeholder="127.0.0.1" value="127.0.0.1" style="flex: 2;">
<input type="number" class="form-control form-control-sm me-2" placeholder="6380" value="6380" style="flex: 1;">
<button type="button" class="btn btn-outline-danger btn-sm" onclick="removeRedisNode(this, 'cluster2')">
<i class="fas fa-minus"></i>
</button>
</div>
</div>
<button type="button" class="btn btn-outline-primary btn-sm mt-2" onclick="addNode('cluster2')">
<button type="button" class="btn btn-outline-primary btn-sm mt-2" onclick="addRedisNode('cluster2')">
<i class="fas fa-plus"></i> 添加节点
</button>
</div>
<div class="mb-3">
<label class="form-label">密码 (可选)</label>
<input type="password" class="form-control" id="cluster2-password" placeholder="Redis密码">
</div>
<div class="row">
<div class="col-6">
<label class="form-label">连接超时 (秒)</label>
<input type="number" class="form-control" id="cluster2-timeout" value="3" min="1" max="30">
<label class="form-label">密码</label>
<input type="password" class="form-control form-control-sm" id="cluster2Password" placeholder="可选">
</div>
<div class="col-6">
<label class="form-label">最大连接数</label>
<input type="number" class="form-control" id="cluster2-max-conn" value="16" min="1" max="100">
</div>
</div>
<div class="mt-3">
<button type="button" class="btn btn-outline-success btn-sm" onclick="testConnection('cluster2')">
<button class="btn btn-outline-primary btn-sm mt-4 w-100" onclick="testConnection('cluster2')">
<i class="fas fa-plug"></i> 测试连接
</button>
</div>
@@ -391,141 +330,189 @@
</div>
</div>
<!-- 查询选项 -->
<div class="row">
<div class="col-lg-12">
<!-- 查询面板和结果展示 -->
<div class="col-lg-8">
<div class="config-section">
<h4><i class="fas fa-search me-2"></i>查询选项</h4>
<h4><i class="fas fa-search"></i> 查询配置</h4>
<div class="mb-3">
<label class="form-label">查询模式</label>
<!-- 查询模式选择 -->
<div class="card mb-3">
<div class="card-header">
<h6><i class="fas fa-sliders-h"></i> 查询模式</h6>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="form-check">
<input class="form-check-input" type="radio" name="queryMode" id="randomMode" value="random" checked>
<input class="form-check-input" type="radio" name="queryMode" id="randomMode" value="random" checked onchange="toggleQueryMode()">
<label class="form-check-label" for="randomMode">
<i class="fas fa-random me-1"></i>随机采样查询
<strong>随机采样模式</strong>
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="queryMode" id="specifiedMode" value="specified">
<label class="form-check-label" for="specifiedMode">
<i class="fas fa-list me-1"></i>指定Key查询
</label>
</div>
</div>
<!-- 随机采样选项 -->
<div id="randomOptions">
<div id="randomOptions" class="mt-2">
<div class="row">
<div class="col-md-4">
<div class="col-6">
<label class="form-label">采样数量</label>
<input type="number" class="form-control" id="sampleCount" value="100" min="1" max="10000">
<input type="number" class="form-control form-control-sm" id="sampleCount" value="100" min="1" max="10000">
</div>
<div class="col-md-4">
<label class="form-label">Key匹配模式</label>
<input type="text" class="form-control" id="keyPattern" value="*" placeholder="例如: user:*">
<div class="col-6">
<label class="form-label">Key模式</label>
<input type="text" class="form-control form-control-sm" id="keyPattern" value="*" placeholder="*">
</div>
<div class="col-md-4">
<label class="form-label">数据源集群</label>
<select class="form-select" id="sourceCluster">
<option value="cluster1">集群1 (生产环境)</option>
<option value="cluster2" selected>集群2 (测试环境)</option>
</div>
<div class="mt-2">
<label class="form-label">源集群</label>
<select class="form-select form-select-sm" id="sourceCluster">
<option value="cluster1">集群1获取Key</option>
<option value="cluster2" selected>从集群2获取Key</option>
</select>
</div>
</div>
</div>
<!-- 指定Key选项 -->
<div id="specifiedOptions" style="display: none;">
<div class="mb-3">
<label class="form-label">指定Key列表 (每行一个)</label>
<textarea class="form-control query-keys" id="specifiedKeys"
placeholder="请输入要查询的Key每行一个&#10;例如:&#10;user:1001&#10;user:1002&#10;session:abc123"></textarea>
<div class="col-md-6">
<div class="form-check">
<input class="form-check-input" type="radio" name="queryMode" id="specifiedMode" value="specified" onchange="toggleQueryMode()">
<label class="form-check-label" for="specifiedMode">
<strong>指定Key模式</strong>
</label>
</div>
<div id="specifiedOptions" class="mt-2" style="display: none;">
<label class="form-label">Key列表 (每行一个)</label>
<textarea class="form-control query-keys" id="specifiedKeys" rows="6" placeholder="输入要查询的Key每行一个&#10;例如:&#10;user:1001&#10;user:1002&#10;session:abc123"></textarea>
<small class="form-text text-muted">支持大批量Key查询建议单次不超过1000个</small>
</div>
</div>
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="row">
<div class="col-12">
<div class="text-center">
<!-- 执行按钮 -->
<div class="text-center mb-4">
<button type="button" class="btn btn-redis btn-lg me-3" onclick="executeRedisComparison()">
<i class="fas fa-play me-2"></i>开始比较
<i class="fas fa-play me-2"></i>开始Redis数据比较
</button>
<button type="button" class="btn btn-outline-secondary btn-lg" onclick="clearResults()">
<i class="fas fa-eraser me-2"></i>清空结果
</button>
<div class="loading mt-3">
<div class="spinner-border text-danger me-2" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<span>正在执行Redis数据比较请稍候...</span>
</div>
</div>
</div>
</div>
<!-- 结果展示区域 -->
<div class="result-section" id="results" style="display: none;">
<!-- 统计卡片 -->
<div class="row" id="statsCards">
<!-- 统计卡片将通过JavaScript动态生成 -->
<div class="result-section" id="resultSection" style="display: none;">
<!-- 统计信息 -->
<div class="row" id="stats">
<!-- 统计卡片将在这里动态生成 -->
</div>
<!-- 详细结果 -->
<div class="row">
<div class="col-12">
<ul class="nav nav-tabs" id="resultTabs" role="tablist">
<!-- 结果选项卡导航 -->
<div class="card mt-4">
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs" id="resultTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="differences-tab" data-bs-toggle="tab" data-bs-target="#differences" type="button" role="tab">
<i class="fas fa-exclamation-triangle me-1"></i>差异数据 (<span id="diff-count">0</span>)
<button class="nav-link active" id="differences-tab" data-bs-toggle="tab" data-bs-target="#differences-panel" type="button" role="tab">
<i class="fas fa-exclamation-triangle"></i> 差异详情 <span class="badge bg-danger ms-1" id="diff-count">0</span>
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="identical-tab" data-bs-toggle="tab" data-bs-target="#identical" type="button" role="tab">
<i class="fas fa-check-circle me-1"></i>相同数据 (<span id="identical-count">0</span>)
<button class="nav-link" id="identical-tab" data-bs-toggle="tab" data-bs-target="#identical-panel" type="button" role="tab">
<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="missing-tab" data-bs-toggle="tab" data-bs-target="#missing" type="button" role="tab">
<i class="fas fa-question-circle me-1"></i>缺失数据 (<span id="missing-count">0</span>)
<button class="nav-link" id="missing-tab" data-bs-toggle="tab" data-bs-target="#missing-panel" type="button" role="tab">
<i class="fas fa-question-circle"></i> 缺失数据 <span class="badge bg-warning ms-1" id="missing-count">0</span>
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="performance-tab" data-bs-toggle="tab" data-bs-target="#performance" type="button" role="tab">
<i class="fas fa-chart-line me-1"></i>性能报告
<button class="nav-link" id="raw-data-tab" data-bs-toggle="tab" data-bs-target="#raw-data-panel" type="button" role="tab">
<i class="fas fa-database"></i> 原生数据
</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> 比较总结
</button>
</li>
</ul>
</div>
<div class="card-body">
<div class="tab-content" id="resultTabContent">
<!-- 差异数据 -->
<div class="tab-pane fade show active" id="differences" role="tabpanel">
<div class="mt-3" id="differenceResults">
<!-- 差异结果将通过JavaScript动态生成 -->
<!-- 差异详情面板 -->
<div class="tab-pane fade show active" id="differences-panel" role="tabpanel">
<div id="differences-content">
<!-- 差异内容将在这里动态生成 -->
</div>
<div id="differences-pagination" class="d-flex justify-content-center mt-3">
<!-- 分页将在这里动态生成 -->
</div>
</div>
<!-- 相同数据 -->
<div class="tab-pane fade" id="identical" role="tabpanel">
<div class="mt-3" id="identicalResults">
<!-- 相同结果将通过JavaScript动态生成 -->
<!-- 相同结果面板 -->
<div class="tab-pane fade" id="identical-panel" role="tabpanel">
<div id="identical-content">
<!-- 相同结果内容将在这里动态生成 -->
</div>
<div id="identical-pagination" class="d-flex justify-content-center mt-3">
<!-- 分页将在这里动态生成 -->
</div>
</div>
<!-- 缺失数据 -->
<div class="tab-pane fade" id="missing" role="tabpanel">
<div class="mt-3" id="missingResults">
<!-- 缺失结果将通过JavaScript动态生成 -->
<!-- 缺失数据面板 -->
<div class="tab-pane fade" id="missing-panel" role="tabpanel">
<div id="missing-content">
<!-- 缺失数据内容将在这里动态生成 -->
</div>
<div id="missing-pagination" class="d-flex justify-content-center mt-3">
<!-- 分页将在这里动态生成 -->
</div>
</div>
<!-- 性能报告 -->
<div class="tab-pane fade" id="performance" role="tabpanel">
<div class="performance-section mt-3" id="performanceReport">
<!-- 性能报告将通过JavaScript动态生成 -->
<!-- 原生数据面板 -->
<div class="tab-pane fade" id="raw-data-panel" role="tabpanel">
<div class="row">
<div class="col-md-6">
<h5><i class="fas fa-server text-primary"></i> 集群1 原生数据</h5>
<div class="mb-3">
<div class="btn-group btn-group-sm" role="group">
<button type="button" class="btn btn-outline-primary active" onclick="switchRawDataView('cluster1', 'formatted')">格式化</button>
<button type="button" class="btn btn-outline-primary" onclick="switchRawDataView('cluster1', 'raw')">原始</button>
<button type="button" class="btn btn-outline-primary" onclick="exportRawData('cluster1')">导出</button>
</div>
</div>
<div id="cluster1-raw-data" class="redis-value" style="max-height: 500px; overflow-y: auto;">
<!-- 集群1原生数据将在这里显示 -->
</div>
</div>
<div class="col-md-6">
<h5><i class="fas fa-server text-success"></i> 集群2 原生数据</h5>
<div class="mb-3">
<div class="btn-group btn-group-sm" role="group">
<button type="button" class="btn btn-outline-success active" onclick="switchRawDataView('cluster2', 'formatted')">格式化</button>
<button type="button" class="btn btn-outline-success" onclick="switchRawDataView('cluster2', 'raw')">原始</button>
<button type="button" class="btn btn-outline-success" onclick="exportRawData('cluster2')">导出</button>
</div>
</div>
<div id="cluster2-raw-data" class="redis-value" style="max-height: 500px; overflow-y: auto;">
<!-- 集群2原生数据将在这里显示 -->
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12">
<div class="alert alert-info">
<i class="fas fa-info-circle"></i>
<strong>提示:</strong>原生数据显示实际从Redis查询到的数据。格式化视图便于阅读原始视图保持数据原始格式。
</div>
</div>
</div>
</div>
<!-- 比较总结面板 -->
<div class="tab-pane fade" id="summary-panel" role="tabpanel">
<div id="performanceReport">
<!-- 性能报告将在这里动态生成 -->
</div>
</div>
</div>
</div>
</div>
@@ -534,22 +521,32 @@
</div>
</div>
<!-- Redis配置组保存模态框 -->
<!-- 加载提示 -->
<div class="loading" id="loadingIndicator">
<div class="position-fixed top-50 start-50 translate-middle bg-dark text-white p-4 rounded shadow" style="z-index: 9999;">
<div class="d-flex align-items-center">
<div class="spinner-border spinner-border-sm me-3" role="status"></div>
<span id="loadingText">正在执行Redis数据比较请稍候...</span>
</div>
</div>
</div>
<!-- 配置组保存对话框 -->
<div class="modal fade" id="saveRedisConfigModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-save me-2"></i>保存Redis配置组</h5>
<h5 class="modal-title">保存Redis配置组</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">配置组名称 *</label>
<input type="text" class="form-control" id="redisConfigGroupName" placeholder="输入配置组名称">
<label for="redisConfigName" class="form-label">配置组名称</label>
<input type="text" class="form-control" id="redisConfigName" placeholder="输入配置组名称">
</div>
<div class="mb-3">
<label class="form-label">描述</label>
<textarea class="form-control" id="redisConfigGroupDescription" placeholder="输入配置组描述(可选)"></textarea>
<label for="redisConfigDescription" class="form-label">描述</label>
<textarea class="form-control" id="redisConfigDescription" rows="3" placeholder="配置组描述(可选)"></textarea>
</div>
</div>
<div class="modal-footer">
@@ -560,12 +557,12 @@
</div>
</div>
<!-- Redis配置管理模态-->
<!-- 配置管理对话-->
<div class="modal fade" id="manageRedisConfigModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-cog me-2"></i>Redis配置组管理</h5>
<h5 class="modal-title">管理Redis配置组</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
@@ -573,35 +570,88 @@
<!-- 配置组列表将在这里动态生成 -->
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
</div>
</div>
</div>
<!-- 历史记录管理对话框 -->
<div class="modal fade" id="manageRedisHistoryModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">管理Redis查询历史</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div id="redisHistoryList">
<!-- 历史记录列表将在这里动态生成 -->
</div>
</div>
</div>
</div>
</div>
<!-- Redis配置导入模态-->
<!-- 查询日志对话-->
<div class="modal fade" id="redisQueryLogsModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Redis查询日志</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="d-flex justify-content-end mb-3">
<button class="btn btn-sm btn-outline-primary me-2" onclick="refreshRedisQueryLogs()">
<i class="fas fa-sync-alt"></i> 刷新
</button>
<button class="btn btn-sm btn-outline-danger" onclick="clearRedisQueryLogs()">
<i class="fas fa-trash"></i> 清空日志
</button>
</div>
<div class="log-viewer" id="redisQueryLogs">
<!-- 查询日志将在这里动态生成 -->
</div>
</div>
</div>
</div>
</div>
<!-- 导入配置对话框 -->
<div class="modal fade" id="importRedisConfigModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-download me-2"></i>导入Redis配置</h5>
<h5 class="modal-title">导入Redis配置</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="alert alert-info">
<strong>支持的配置格式:</strong><br>
clusterName: "redis-test"<br>
clusterAddress: "10.20.2.109:6470"<br>
clusterPassword: ""<br>
cachePrefix: message.status.Writer.<br>
cacheTtl: 2000<br>
async: true
<div class="mb-3">
<label for="configFormat" class="form-label">配置格式</label>
<select class="form-select" id="configFormat" onchange="updateConfigTemplate()">
<option value="yaml">YAML格式</option>
<option value="json">JSON格式</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">配置内容</label>
<textarea class="form-control" id="redisConfigImportText" rows="8"
placeholder="请粘贴Redis配置内容..."></textarea>
<label for="configContent" class="form-label">配置内容</label>
<textarea class="form-control" id="configContent" rows="15" placeholder="请粘贴配置内容..."></textarea>
<small class="form-text text-muted">支持YAML和JSON格式的Redis配置</small>
</div>
<div class="mb-3">
<div class="accordion" id="configTemplateAccordion">
<div class="accordion-item">
<h2 class="accordion-header" id="templateHeading">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#templateCollapse">
<i class="fas fa-code me-2"></i>配置示例
</button>
</h2>
<div id="templateCollapse" class="accordion-collapse collapse" data-bs-parent="#configTemplateAccordion">
<div class="accordion-body">
<pre id="configTemplate" class="bg-light p-3 rounded" style="font-size: 0.85em;"></pre>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
@@ -612,84 +662,6 @@
</div>
</div>
<!-- Redis查询历史保存模态框 -->
<div class="modal fade" id="saveRedisHistoryModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-bookmark me-2"></i>保存Redis查询历史</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">历史记录名称 *</label>
<input type="text" class="form-control" id="redisHistoryName" placeholder="输入历史记录名称">
</div>
<div class="mb-3">
<label class="form-label">描述</label>
<textarea class="form-control" id="redisHistoryDescription" placeholder="输入历史记录描述(可选)"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-success" onclick="saveRedisQueryHistory()">保存</button>
</div>
</div>
</div>
</div>
<!-- Redis查询历史管理模态框 -->
<div class="modal fade" id="manageRedisHistoryModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-list me-2"></i>Redis查询历史管理</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div id="redisHistoryList">
<!-- 查询历史列表将在这里动态生成 -->
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
<!-- Redis查询日志模态框 -->
<div class="modal fade" id="redisQueryLogsModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-file-alt me-2"></i>Redis查询日志</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="mb-0">Redis查询执行日志</h6>
<div>
<button class="btn btn-sm btn-outline-primary me-2" onclick="refreshRedisQueryLogs()">
<i class="fas fa-sync-alt"></i> 刷新
</button>
<button class="btn btn-sm btn-outline-danger" onclick="clearRedisQueryLogs()">
<i class="fas fa-trash"></i> 清空
</button>
</div>
</div>
<div id="redisQueryLogs" class="log-viewer">
<!-- 查询日志将在这里动态生成 -->
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
<!-- 引入必要的JavaScript -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="/static/js/redis_compare.js"></script>
</body>