From 6558541a6f0c00b31ca27d052bd9065cda2676e3 Mon Sep 17 00:00:00 2001 From: YoVinchen Date: Sat, 2 Aug 2025 16:55:33 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=E4=BB=A5=E5=89=8D=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 65 +++++++++++------- app.py | 53 +++++++------- static/js/app.js | 63 +++++++++++------ templates/db_compare.html | 141 ++++++++++++++++++++------------------ 4 files changed, 182 insertions(+), 140 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b224e28..cb6d462 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -55,11 +55,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - 生产环境单表 + 测试环境分表 - 生产环境单表 + 测试环境单表 -**示例代码** -- `demo/Query.py`: 独立的Cassandra查询比对脚本示例 -- `demo/twcsQuery.py`: 另一个查询示例 -- `demo/CalculationLibrary.py`: 分表计算逻辑参考实现 -- `test_sharding.py`: 分表功能测试脚本 +**核心文件** +- `app.py`: 唯一的主应用文件,包含所有功能实现 +- `config_groups.db`: SQLite数据库文件 ### 关键功能模块 @@ -88,38 +86,34 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ### 环境设置 ```bash -# 安装依赖(仅需要Flask和cassandra-driver) +# 安装依赖 pip install -r requirements.txt # 运行应用(默认端口5000) python app.py # 自定义端口运行 -# 修改app.py最后一行:app.run(debug=True, port=5001) +# 修改app.py最后一行:app.run(debug=True, port=XXXX) +``` + +### 调试和日志 +```bash +# 应用启动后,查询日志可通过以下API获取: +# GET /api/query-logs - 获取查询日志 +# DELETE /api/query-logs - 清空查询日志 + +# 日志级别:INFO, WARNING, ERROR +# 所有Cassandra查询和SQL操作都会记录到查询日志中 ``` ### 测试和验证 ```bash -# 运行分表功能测试(测试时间戳提取和分表索引计算) -python test_sharding.py +# 主要通过Web界面进行功能测试 +# 单表查询测试:http://localhost:5000/db-compare +# 分表查询测试:在Web界面中开启分表模式 -# 测试新的分表计算规则 -python test_new_sharding.py - -# 演示新分表规则的详细工作原理 -python demo_new_sharding.py - -# 测试查询日志功能 -python test_query_logs.py - -# 集成测试(分表功能 + 查询日志) -python test_integration.py - -# 测试数据库连接和查询功能 -# 通过Web界面:http://localhost:5000/db-compare -# 或直接运行示例脚本: -python demo/Query.py -python demo/twcsQuery.py +# 数据库初始化(如果config_groups.db不存在) +# 通过访问Web界面会自动创建数据库表结构 ``` ### 开发模式 @@ -131,6 +125,12 @@ python demo/twcsQuery.py - Flask==2.3.3 - cassandra-driver==3.29.1 +### 项目特点 +- **单文件架构**:所有后端逻辑都在 `app.py` 中实现(1400+行代码) +- **内存日志系统**:使用 `QueryLogCollector` 类在内存中收集查询日志 +- **SQLite本地存储**:配置组和查询历史存储在本地 `config_groups.db` 文件中 +- **前端原生实现**:使用原生JavaScript + Bootstrap,无现代前端框架 + ## API架构说明 ### 核心API端点 @@ -229,6 +229,19 @@ python demo/twcsQuery.py ## 开发注意事项 +### 代码修改指导 +- **单文件开发**:所有后端功能都在 `app.py` 中,修改时要注意代码结构清晰 +- **数据库模式变更**:修改SQLite表结构需要考虑向后兼容性 +- **前端JavaScript**:位于 `static/js/app.js`,使用原生JS,注意浏览器兼容性 +- **HTML模板**:使用Jinja2模板引擎,主要文件在 `templates/` 目录 + +### 核心类和函数位置(app.py) +- `QueryLogCollector`类:日志收集系统(第20-46行) +- `ShardingCalculator`类:分表计算器(第64行开始) +- 数据库连接:`create_cassandra_connection()` +- 查询比对:`execute_query()` 和 `execute_sharding_query()` +- API路由:使用Flask装饰器定义 + ### 分表功能开发指导 - **时间戳解析(已更新)**:`ShardingCalculator.extract_timestamp_from_key()` 新规则 - 使用 `re.sub(r'\D', '', key)` 删除所有非数字字符 diff --git a/app.py b/app.py index 3fe992d..b4fc317 100644 --- a/app.py +++ b/app.py @@ -1,4 +1,4 @@ -from flask import Flask, render_template, request, jsonify +from flask import Flask, render_template, request, jsonify, send_from_directory from cassandra.cluster import Cluster from cassandra.auth import PlainTextAuthProvider import json @@ -455,7 +455,7 @@ DEFAULT_CONFIG = { 'keyspace': '', 'table': '' }, - 'keys': ['docid'], + 'keys': [], 'fields_to_compare': [], 'exclude_fields': [] } @@ -536,7 +536,9 @@ def get_config_group_by_id(group_id): try: cursor.execute(''' - SELECT * FROM config_groups WHERE id = ? + SELECT id, name, description, pro_config, test_config, query_config, + sharding_config, created_at, updated_at + FROM config_groups WHERE id = ? ''', (group_id,)) row = cursor.fetchone() @@ -552,25 +554,12 @@ def get_config_group_by_id(group_id): 'updated_at': row['updated_at'] } - # 添加分表配置(如果存在) - sharding_config_data = None - try: - # 尝试获取sharding_config字段 - sharding_config_data = row[len(row) - 3] # sharding_config在倒数第三个位置 - except (IndexError, KeyError): - # 如果字段不存在,尝试通过列名获取 + # 添加分表配置 + if row['sharding_config']: try: - cursor.execute("PRAGMA table_info(config_groups)") - columns = cursor.fetchall() - column_names = [col[1] for col in columns] - if 'sharding_config' in column_names: - sharding_index = column_names.index('sharding_config') - sharding_config_data = row[sharding_index] - except: - pass - - if sharding_config_data: - config['sharding_config'] = json.loads(sharding_config_data) + config['sharding_config'] = json.loads(row['sharding_config']) + except (json.JSONDecodeError, TypeError): + config['sharding_config'] = None else: config['sharding_config'] = None @@ -1071,6 +1060,11 @@ def generate_recommendations(consistency_percentage, missing_in_test, missing_in def index(): return render_template('index.html') +@app.route('/test-config-load') +def test_config_load(): + """配置加载测试页面""" + return send_from_directory('.', 'test_config_load.html') + @app.route('/db-compare') def db_compare(): return render_template('db_compare.html') @@ -1305,11 +1299,18 @@ def api_save_config_group(): description = data.get('description', '').strip() pro_config = data.get('pro_config', {}) test_config = data.get('test_config', {}) - query_config = { - 'keys': data.get('keys', []), - 'fields_to_compare': data.get('fields_to_compare', []), - 'exclude_fields': data.get('exclude_fields', []) - } + + # 获取查询配置,支持两种格式 + if 'query_config' in data: + # 嵌套格式 + query_config = data.get('query_config', {}) + else: + # 平铺格式 + query_config = { + 'keys': data.get('keys', []), + 'fields_to_compare': data.get('fields_to_compare', []), + 'exclude_fields': data.get('exclude_fields', []) + } # 提取分表配置 sharding_config = data.get('sharding_config') diff --git a/static/js/app.js b/static/js/app.js index 1c4e530..d9dc97b 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -53,8 +53,8 @@ function toggleShardingMode() { shardingConfig.style.display = 'block'; executeButton.textContent = '执行分表查询比对'; keyInputHint.textContent = '分表模式:Key值应包含时间戳用于计算分表索引'; - keysField.placeholder = 'wmid (推荐使用包含时间戳的字段)'; - keysField.value = 'wmid'; + keysField.placeholder = ' (推荐使用包含时间戳的字段)'; + keysField.value = ''; // 更新查询Key输入框的占位符 const queryValues = document.getElementById('query_values'); @@ -64,8 +64,8 @@ function toggleShardingMode() { shardingConfig.style.display = 'none'; executeButton.textContent = '执行查询比对'; keyInputHint.textContent = '单表模式:输入普通Key值'; - keysField.placeholder = 'docid'; - keysField.value = 'docid'; + keysField.placeholder = ''; + keysField.value = ''; // 更新查询Key输入框的占位符 const queryValues = document.getElementById('query_values'); @@ -163,9 +163,33 @@ async function loadSelectedConfigGroup() { document.getElementById('test_table').value = config.test_config.table || ''; // 填充查询配置 + console.log('配置组查询配置:', config.query_config); document.getElementById('keys').value = (config.query_config.keys || []).join(','); document.getElementById('fields_to_compare').value = (config.query_config.fields_to_compare || []).join(','); document.getElementById('exclude_fields').value = (config.query_config.exclude_fields || []).join(','); + console.log('填充后的字段值:'); + console.log('keys:', document.getElementById('keys').value); + console.log('fields_to_compare:', document.getElementById('fields_to_compare').value); + console.log('exclude_fields:', document.getElementById('exclude_fields').value); + + // 加载分表配置 + if (config.sharding_config) { + // 设置分表启用状态 + document.getElementById('enableSharding').checked = config.sharding_config.enabled || false; + toggleShardingMode(); + + // 填充分表配置 + document.getElementById('use_sharding_for_pro').checked = config.sharding_config.use_sharding_for_pro || false; + document.getElementById('use_sharding_for_test').checked = config.sharding_config.use_sharding_for_test || false; + document.getElementById('pro_interval_seconds').value = config.sharding_config.interval_seconds || 604800; + document.getElementById('pro_table_count').value = config.sharding_config.table_count || 14; + document.getElementById('test_interval_seconds').value = config.sharding_config.interval_seconds || 604800; + document.getElementById('test_table_count').value = config.sharding_config.table_count || 14; + } else { + // 禁用分表模式 + document.getElementById('enableSharding').checked = false; + toggleShardingMode(); + } showAlert('success', `配置组 "${config.name}" 加载成功`); } else { @@ -247,7 +271,9 @@ async function saveConfigGroup() { body: JSON.stringify({ name: name, description: description, - ...config, + pro_config: config.pro_config, + test_config: config.test_config, + query_config: config.query_config, sharding_config: shardingConfig }) }); @@ -492,11 +518,13 @@ function getCurrentConfig() { keyspace: document.getElementById('test_keyspace').value, table: document.getElementById('test_table').value }, - keys: document.getElementById('keys').value.split(',').map(k => k.trim()).filter(k => k), - fields_to_compare: document.getElementById('fields_to_compare').value - .split(',').map(f => f.trim()).filter(f => f), - exclude_fields: document.getElementById('exclude_fields').value - .split(',').map(f => f.trim()).filter(f => f), + query_config: { + keys: document.getElementById('keys').value.split(',').map(k => k.trim()).filter(k => k), + fields_to_compare: document.getElementById('fields_to_compare').value + .split(',').map(f => f.trim()).filter(f => f), + exclude_fields: document.getElementById('exclude_fields').value + .split(',').map(f => f.trim()).filter(f => f) + }, values: document.getElementById('query_values').value .split('\n').map(v => v.trim()).filter(v => v) }; @@ -512,7 +540,7 @@ async function executeQuery() { return; } - if (!config.keys.length) { + if (!config.query_config.keys.length) { showAlert('warning', '请输入主键字段'); return; } @@ -573,12 +601,11 @@ function getShardingConfig() { return { ...baseConfig, sharding_config: { + 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 } }; } @@ -2660,11 +2687,7 @@ async function saveHistoryRecord() { description: description, pro_config: config.pro_config, test_config: config.test_config, - query_config: { - keys: config.keys, - fields_to_compare: config.fields_to_compare, - exclude_fields: config.exclude_fields - }, + query_config: config.query_config, query_keys: config.values, results_summary: currentResults.summary || {}, execution_time: 0.0, diff --git a/templates/db_compare.html b/templates/db_compare.html index ad30877..71e330e 100644 --- a/templates/db_compare.html +++ b/templates/db_compare.html @@ -299,72 +299,6 @@

配置管理

- -
-
-
查询模式
-
-
-
- - -
-
-
- - - -
@@ -419,6 +353,77 @@
+ +
+
+
查询模式
+
+
+
+ + +
+
+
+ + + +
@@ -533,8 +538,8 @@
- - 分表模式下推荐使用包含时间戳的字段如wmid + + 分表模式下推荐使用包含时间戳的字段如