Compare commits
2 Commits
211bcc9066
...
master
Author | SHA1 | Date | |
---|---|---|---|
fe2803f3da | |||
9ed05dd58d |
@@ -20,7 +20,8 @@ from .config_manager import (
|
||||
save_redis_query_history, get_redis_query_history,
|
||||
get_redis_query_history_by_id, delete_redis_query_history,
|
||||
batch_delete_redis_query_history,
|
||||
parse_redis_config_from_yaml
|
||||
parse_redis_config_from_yaml,
|
||||
convert_bytes_to_str # 添加bytes转换函数
|
||||
)
|
||||
from .cassandra_client import create_connection
|
||||
from .query_engine import execute_query, execute_mixed_query
|
||||
@@ -266,6 +267,8 @@ def setup_routes(app, query_log_collector):
|
||||
|
||||
# 结束查询批次
|
||||
query_log_collector.end_current_batch()
|
||||
# 转换result中可能包含的bytes类型数据
|
||||
result = convert_bytes_to_str(result)
|
||||
return jsonify(result)
|
||||
|
||||
except Exception as e:
|
||||
@@ -421,6 +424,8 @@ def setup_routes(app, query_log_collector):
|
||||
|
||||
# 结束查询批次
|
||||
query_log_collector.end_current_batch()
|
||||
# 转换result中可能包含的bytes类型数据
|
||||
result = convert_bytes_to_str(result)
|
||||
return jsonify(result)
|
||||
|
||||
except Exception as e:
|
||||
@@ -1217,7 +1222,7 @@ def setup_routes(app, query_log_collector):
|
||||
# 过滤每个批次中的日志,只保留Redis相关的
|
||||
redis_logs = [
|
||||
log for log in logs
|
||||
if log.get('query_type') == 'redis' or
|
||||
if log.get('query_type', '').lower() == 'redis' or
|
||||
(log.get('message') and 'redis' in log.get('message', '').lower())
|
||||
]
|
||||
if redis_logs: # 只有当批次中有Redis日志时才添加
|
||||
@@ -1239,7 +1244,7 @@ def setup_routes(app, query_log_collector):
|
||||
# 过滤Redis相关的日志
|
||||
redis_logs = [
|
||||
log for log in logs
|
||||
if log.get('query_type') == 'redis' or
|
||||
if log.get('query_type', '').lower() == 'redis' or
|
||||
(log.get('message') and 'redis' in log.get('message', '').lower())
|
||||
]
|
||||
|
||||
|
@@ -39,6 +39,31 @@ from .database import ensure_database, get_db_connection
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def convert_bytes_to_str(obj):
|
||||
"""递归转换对象中的bytes类型为字符串,用于JSON序列化
|
||||
|
||||
Args:
|
||||
obj: 需要转换的对象(可以是dict, list或其他类型)
|
||||
|
||||
Returns:
|
||||
转换后的对象,所有bytes类型都被转换为hex字符串
|
||||
"""
|
||||
if isinstance(obj, bytes):
|
||||
# 将bytes转换为十六进制字符串
|
||||
return obj.hex()
|
||||
elif isinstance(obj, dict):
|
||||
# 递归处理字典
|
||||
return {key: convert_bytes_to_str(value) for key, value in obj.items()}
|
||||
elif isinstance(obj, list):
|
||||
# 递归处理列表
|
||||
return [convert_bytes_to_str(item) for item in obj]
|
||||
elif isinstance(obj, tuple):
|
||||
# 递归处理元组
|
||||
return tuple(convert_bytes_to_str(item) for item in obj)
|
||||
else:
|
||||
# 其他类型直接返回
|
||||
return obj
|
||||
|
||||
# Cassandra数据库默认配置模板
|
||||
# 注意:此配置不包含敏感信息,仅作为UI表单的初始模板使用
|
||||
DEFAULT_CONFIG = {
|
||||
@@ -233,6 +258,9 @@ def save_redis_query_history(name, description, cluster1_config, cluster2_config
|
||||
cursor = conn.cursor()
|
||||
|
||||
try:
|
||||
# 转换可能包含bytes类型的数据
|
||||
raw_results = convert_bytes_to_str(raw_results) if raw_results else None
|
||||
|
||||
cursor.execute('''
|
||||
INSERT INTO redis_query_history
|
||||
(name, description, cluster1_config, cluster2_config, query_options, query_keys,
|
||||
@@ -600,6 +628,11 @@ def save_query_history(name, description, pro_config, test_config, query_config,
|
||||
cursor = conn.cursor()
|
||||
|
||||
try:
|
||||
# 转换可能包含bytes类型的数据
|
||||
raw_results = convert_bytes_to_str(raw_results) if raw_results else None
|
||||
differences_data = convert_bytes_to_str(differences_data) if differences_data else None
|
||||
identical_data = convert_bytes_to_str(identical_data) if identical_data else None
|
||||
|
||||
cursor.execute('''
|
||||
INSERT INTO query_history
|
||||
(name, description, pro_config, test_config, query_config, query_keys,
|
||||
|
@@ -86,8 +86,8 @@ def compare_results(pro_data, test_data, keys, fields_to_compare, exclude_fields
|
||||
value_pro = getattr(row_pro, column)
|
||||
value_test = getattr(row_test, column)
|
||||
|
||||
# 使用智能比较函数
|
||||
if not compare_values(value_pro, value_test):
|
||||
# 使用智能比较函数,传递字段名用于标签字段判断
|
||||
if not compare_values(value_pro, value_test, column):
|
||||
has_difference = True
|
||||
# 格式化显示值
|
||||
formatted_pro_value = format_json_for_display(value_pro)
|
||||
@@ -99,7 +99,8 @@ def compare_results(pro_data, test_data, keys, fields_to_compare, exclude_fields
|
||||
'pro_value': formatted_pro_value,
|
||||
'test_value': formatted_test_value,
|
||||
'is_json': is_json_field(value_pro) or is_json_field(value_test),
|
||||
'is_array': is_json_array_field(value_pro) or is_json_array_field(value_test)
|
||||
'is_array': is_json_array_field(value_pro) or is_json_array_field(value_test),
|
||||
'is_tag': is_tag_field(column, value_pro) or is_tag_field(column, value_test)
|
||||
})
|
||||
|
||||
# 统计字段差异次数
|
||||
@@ -307,9 +308,54 @@ def is_json_field(value):
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
return False
|
||||
|
||||
def compare_values(value1, value2):
|
||||
"""智能比较两个值,支持JSON标准化和数组比较"""
|
||||
# 首先检查是否为数组类型
|
||||
def is_tag_field(field_name, value):
|
||||
"""判断是否为标签类字段(空格分隔的标签列表)
|
||||
|
||||
标签字段特征:
|
||||
1. 字段名包含 'tag' 关键字
|
||||
2. 值是字符串类型
|
||||
3. 包含空格分隔的多个元素
|
||||
"""
|
||||
if not isinstance(value, str):
|
||||
return False
|
||||
|
||||
# 检查字段名是否包含tag
|
||||
if field_name and 'tag' in field_name.lower():
|
||||
# 检查是否包含空格分隔的多个元素
|
||||
elements = value.strip().split()
|
||||
if len(elements) > 1:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def compare_tag_values(value1, value2):
|
||||
"""比较标签类字段的值(忽略顺序)
|
||||
|
||||
将空格分隔的标签字符串拆分成集合进行比较
|
||||
"""
|
||||
if not isinstance(value1, str) or not isinstance(value2, str):
|
||||
return value1 == value2
|
||||
|
||||
# 将标签字符串拆分成集合
|
||||
tags1 = set(value1.strip().split())
|
||||
tags2 = set(value2.strip().split())
|
||||
|
||||
# 比较集合是否相等(忽略顺序)
|
||||
return tags1 == tags2
|
||||
|
||||
def compare_values(value1, value2, field_name=None):
|
||||
"""智能比较两个值,支持JSON标准化、数组比较和标签比较
|
||||
|
||||
Args:
|
||||
value1: 第一个值
|
||||
value2: 第二个值
|
||||
field_name: 字段名(可选,用于判断是否为标签字段)
|
||||
"""
|
||||
# 检查是否为标签字段
|
||||
if field_name and (is_tag_field(field_name, value1) or is_tag_field(field_name, value2)):
|
||||
return compare_tag_values(value1, value2)
|
||||
|
||||
# 检查是否为数组类型
|
||||
if is_json_array_field(value1) or is_json_array_field(value2):
|
||||
return compare_array_values(value1, value2)
|
||||
|
||||
|
@@ -900,9 +900,9 @@ async function loadRedisConfigGroupsForManagement() {
|
||||
const response = await fetch('/api/redis/config-groups');
|
||||
const result = await response.json();
|
||||
|
||||
const container = document.getElementById('redisConfigGroupList');
|
||||
const container = document.getElementById('redisConfigGroupsList');
|
||||
if (!container) {
|
||||
console.error('Redis配置组列表容器未找到: redisConfigGroupList');
|
||||
console.error('Redis配置组列表容器未找到: redisConfigGroupsList');
|
||||
showAlert('Redis配置组列表容器未找到,请检查页面结构', 'danger');
|
||||
return;
|
||||
}
|
||||
@@ -938,7 +938,7 @@ async function loadRedisConfigGroupsForManagement() {
|
||||
console.log('没有找到Redis配置组数据');
|
||||
}
|
||||
} catch (error) {
|
||||
const container = document.getElementById('redisConfigGroupList');
|
||||
const container = document.getElementById('redisConfigGroupsList');
|
||||
if (container) {
|
||||
container.innerHTML = '<div class="alert alert-danger">加载失败: ' + error.message + '</div>';
|
||||
}
|
||||
@@ -2195,80 +2195,6 @@ async function saveRedisConfigGroup() {
|
||||
}
|
||||
}
|
||||
|
||||
// 显示管理Redis配置组对话框
|
||||
function showManageRedisConfigDialog() {
|
||||
loadRedisConfigGroupsForManagement();
|
||||
new bootstrap.Modal(document.getElementById('manageRedisConfigModal')).show();
|
||||
}
|
||||
|
||||
// 为管理界面加载Redis配置组
|
||||
async function loadRedisConfigGroupsForManagement() {
|
||||
try {
|
||||
const response = await fetch('/api/redis/config-groups');
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
displayRedisConfigGroupsForManagement(result.data);
|
||||
} else {
|
||||
showAlert(`加载配置组失败: ${result.error}`, 'danger');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载Redis配置组失败:', error);
|
||||
showAlert(`加载失败: ${error.message}`, 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
// 显示Redis配置组管理列表
|
||||
function displayRedisConfigGroupsForManagement(configGroups) {
|
||||
const container = document.getElementById('redisConfigGroupsList');
|
||||
|
||||
if (!configGroups || configGroups.length === 0) {
|
||||
container.innerHTML = '<div class="text-center text-muted py-4">暂无保存的配置组</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
configGroups.forEach(config => {
|
||||
html += `
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div class="flex-grow-1">
|
||||
<h6 class="card-title mb-2">
|
||||
<i class="fas fa-cog redis-logo me-2"></i>${config.name}
|
||||
</h6>
|
||||
<p class="card-text text-muted small mb-2">${config.description || '无描述'}</p>
|
||||
<div class="small text-muted">
|
||||
<i class="fas fa-clock me-1"></i>创建时间: ${config.created_at}
|
||||
${config.updated_at !== config.created_at ? `<br><i class="fas fa-edit me-1"></i>更新时间: ${config.updated_at}` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group-vertical btn-group-sm">
|
||||
<button class="btn btn-outline-primary btn-sm mb-1" onclick="loadRedisConfigGroup(${config.id})" title="加载配置">
|
||||
<i class="fas fa-download"></i> 加载
|
||||
</button>
|
||||
<button class="btn btn-outline-success btn-sm mb-1" onclick="exportRedisConfigGroup(${config.id}, '${config.name}')" title="导出YAML">
|
||||
<i class="fas fa-file-export"></i> 导出
|
||||
</button>
|
||||
<button class="btn btn-outline-info btn-sm mb-1" onclick="copyRedisConfigGroup(${config.id}, '${config.name}')" title="复制配置">
|
||||
<i class="fas fa-copy"></i> 复制
|
||||
</button>
|
||||
<button class="btn btn-outline-warning btn-sm mb-1" onclick="testRedisConfigConnection(${config.id})" title="测试连接">
|
||||
<i class="fas fa-plug"></i> 测试
|
||||
</button>
|
||||
<button class="btn btn-outline-danger btn-sm" onclick="deleteRedisConfigGroup(${config.id}, '${config.name}')" title="删除配置">
|
||||
<i class="fas fa-trash"></i> 删除
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
// 刷新Redis配置组列表
|
||||
function refreshRedisConfigGroups() {
|
||||
loadRedisConfigGroupsForManagement();
|
||||
|
@@ -674,22 +674,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 配置组管理对话框 -->
|
||||
<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">管理Redis配置组</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="redisConfigGroupList">
|
||||
<!-- 配置组列表将在这里动态生成 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 导入配置模态框 -->
|
||||
<div class="modal fade" id="importConfigModal" tabindex="-1" aria-labelledby="importConfigModalLabel" aria-hidden="true">
|
||||
|
Reference in New Issue
Block a user