Compare commits
9 Commits
0ac375eb50
...
211bcc9066
Author | SHA1 | Date | |
---|---|---|---|
211bcc9066 | |||
8c3f3df826 | |||
8d7c4e3730 | |||
cdf7e36ba3 | |||
fbca92ba77 | |||
0b1dc6b8ca | |||
eabca97350 | |||
01e323a7ba | |||
d42cefd9ca |
132
.dockerignore
Normal file
132
.dockerignore
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
# BigDataTool Docker 构建忽略文件
|
||||||
|
# 排除不需要打包到镜像中的文件和目录
|
||||||
|
|
||||||
|
# 版本控制
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.gitattributes
|
||||||
|
|
||||||
|
# IDE和编辑器文件
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Python缓存文件
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# 虚拟环境
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# 测试文件
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
|
.pytest_cache/
|
||||||
|
htmlcov/
|
||||||
|
.coverage.*
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
.hypothesis/
|
||||||
|
|
||||||
|
# 文档构建
|
||||||
|
docs/_build/
|
||||||
|
.sphinx/
|
||||||
|
|
||||||
|
# 日志文件
|
||||||
|
*.log
|
||||||
|
logs/*.log
|
||||||
|
|
||||||
|
# 本地数据库文件(可选,如果需要持久化可以移除这行)
|
||||||
|
*.db
|
||||||
|
*.sqlite
|
||||||
|
*.sqlite3
|
||||||
|
|
||||||
|
# 配置文件(敏感信息)
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
*.pem
|
||||||
|
*.key
|
||||||
|
config/secrets/
|
||||||
|
|
||||||
|
# Docker相关文件
|
||||||
|
Dockerfile.dev
|
||||||
|
docker-compose.dev.yml
|
||||||
|
docker-compose.override.yml
|
||||||
|
|
||||||
|
# 临时文件
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
|
tmp/
|
||||||
|
temp/
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
.DS_Store
|
||||||
|
.DS_Store?
|
||||||
|
._*
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
ehthumbs.db
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
*.exe
|
||||||
|
*.msi
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# Linux
|
||||||
|
*~
|
||||||
|
|
||||||
|
# 备份文件
|
||||||
|
*.bak
|
||||||
|
*.backup
|
||||||
|
*.old
|
||||||
|
|
||||||
|
# 本地开发文件
|
||||||
|
local/
|
||||||
|
.local/
|
||||||
|
dev/
|
||||||
|
|
||||||
|
# 缓存目录
|
||||||
|
.cache/
|
||||||
|
*.cache
|
||||||
|
|
||||||
|
# 运行时文件
|
||||||
|
*.pid
|
||||||
|
*.sock
|
||||||
|
|
||||||
|
# 监控和性能分析
|
||||||
|
*.prof
|
||||||
|
*.pstats
|
||||||
|
|
||||||
|
# 其他不需要的文件
|
||||||
|
README.dev.md
|
||||||
|
CONTRIBUTING.md
|
||||||
|
.github/
|
||||||
|
scripts/dev/
|
394
CLAUDE.md
394
CLAUDE.md
@@ -1,394 +0,0 @@
|
|||||||
# CLAUDE.md
|
|
||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
||||||
|
|
||||||
## 项目架构
|
|
||||||
|
|
||||||
这是一个基于Flask的现代化数据库查询比对工具,支持Cassandra和Redis两大数据源的数据一致性验证。采用模块化架构设计,支持单表查询、TWCS分表查询、多主键查询和Redis集群比对等多种复杂场景。
|
|
||||||
|
|
||||||
### 核心组件架构
|
|
||||||
|
|
||||||
**主应用 (app.py)**
|
|
||||||
- 应用入口和全局配置管理
|
|
||||||
- 模块导入和路由设置
|
|
||||||
- 日志系统初始化
|
|
||||||
|
|
||||||
**模块化后端 (modules/)**
|
|
||||||
- `api_routes.py`: 所有Flask路由和请求处理逻辑
|
|
||||||
- Cassandra查询API:`/api/query`, `/api/sharding-query`
|
|
||||||
- Redis比对API:`/api/redis/compare`
|
|
||||||
- 配置管理API:配置组CRUD操作
|
|
||||||
- 查询历史API:历史记录的保存和回放
|
|
||||||
- `database.py`: SQLite数据库管理
|
|
||||||
- 数据库初始化和表结构创建
|
|
||||||
- 版本控制和字段动态添加
|
|
||||||
- 事务处理和连接管理
|
|
||||||
- `query_engine.py`: Cassandra查询引擎
|
|
||||||
- 单表查询和分表查询执行
|
|
||||||
- 多主键查询支持(复合主键SQL构建)
|
|
||||||
- 并行查询和性能优化
|
|
||||||
- `redis_query.py`: Redis查询引擎
|
|
||||||
- 全Redis数据类型支持(String/Hash/List/Set/ZSet/Stream)
|
|
||||||
- 随机采样和指定Key两种查询模式
|
|
||||||
- 集群模式的自动检测和连接
|
|
||||||
- `data_comparison.py`: 数据比对引擎
|
|
||||||
- JSON和数组的智能深度比较
|
|
||||||
- 复合主键的精确匹配算法
|
|
||||||
- 数据质量评估和建议生成
|
|
||||||
- `cassandra_client.py` / `redis_client.py`: 数据库客户端
|
|
||||||
- 连接管理和错误处理
|
|
||||||
- 性能监控和连接池优化
|
|
||||||
- `config_groups.db`: SQLite数据库,存储配置组、查询历史和日志
|
|
||||||
|
|
||||||
**前端 (原生JavaScript + Bootstrap)**
|
|
||||||
- `templates/index.html`: 工具集合首页,提供功能导航
|
|
||||||
- `templates/db_compare.html`: Cassandra比对界面
|
|
||||||
- 支持单表、分表和多主键三种查询模式
|
|
||||||
- 分表模式切换和参数配置
|
|
||||||
- 实时查询日志和性能监控
|
|
||||||
- `templates/redis_compare.html`: Redis比对界面
|
|
||||||
- 集群配置和连接管理
|
|
||||||
- 随机采样和指定Key两种查询模式
|
|
||||||
- 全数据类型支持的结果展示
|
|
||||||
- `static/js/app.js`: Cassandra查询的前端逻辑
|
|
||||||
- 配置管理和表单处理
|
|
||||||
- 分页展示和数据可视化
|
|
||||||
- 多主键查询的UI适配
|
|
||||||
- `static/js/redis_compare.js`: Redis比对的前端逻辑
|
|
||||||
- Redis集群配置管理
|
|
||||||
- 查询模式切换和参数设置
|
|
||||||
- 多类型数据的格式化展示
|
|
||||||
|
|
||||||
### 关键功能模块
|
|
||||||
|
|
||||||
**Cassandra数据比对引擎**
|
|
||||||
- 支持复杂JSON字段的深度比较
|
|
||||||
- 数组字段的顺序无关比较
|
|
||||||
- 字段级别的差异统计和分析
|
|
||||||
- 数据质量评估和建议生成
|
|
||||||
- 支持包含和排除特定字段的比较
|
|
||||||
- 多主键数据比对:支持复合主键的精确匹配和差异检测
|
|
||||||
|
|
||||||
**Redis数据比对引擎**
|
|
||||||
- 全数据类型支持:String、Hash、List、Set、ZSet、Stream
|
|
||||||
- 智能JSON检测和深度比较
|
|
||||||
- 集群和单节点模式的自动适配
|
|
||||||
- 随机采样和指定Key两种查询模式
|
|
||||||
- 性能监控和连接时间统计
|
|
||||||
|
|
||||||
**分表查询功能模块**
|
|
||||||
- **时间戳提取算法**:使用 `re.sub(r'\D', '', key)` 删除Key中所有非数字字符
|
|
||||||
- **分表索引计算**:公式 `int(numbers) // interval_seconds % table_count`
|
|
||||||
- **混合查询场景**:支持生产分表+测试单表等组合
|
|
||||||
- **并行查询**:多分表同时查询以提高性能
|
|
||||||
|
|
||||||
**多主键查询功能模块**
|
|
||||||
- **复合主键格式**:主键字段逗号分隔,查询值逗号分隔
|
|
||||||
- **SQL构建逻辑**:自动选择IN查询或OR条件组合
|
|
||||||
- **数据匹配算法**:统一处理单主键和复合主键匹配
|
|
||||||
- **向后兼容**:完全兼容现有单主键查询
|
|
||||||
|
|
||||||
**用户界面特性**
|
|
||||||
- 分页系统(差异记录和相同记录)
|
|
||||||
- 实时搜索和过滤
|
|
||||||
- 原生数据展示(JSON语法高亮、树形视图)
|
|
||||||
- 配置导入导出和管理
|
|
||||||
- 详细的错误诊断和故障排查指南
|
|
||||||
- 查询历史记录和复用
|
|
||||||
- 查询日志系统:实时显示SQL执行日志,支持日志级别过滤
|
|
||||||
|
|
||||||
## 开发相关命令
|
|
||||||
|
|
||||||
### 环境设置
|
|
||||||
```bash
|
|
||||||
# 安装依赖
|
|
||||||
pip install -r requirements.txt
|
|
||||||
|
|
||||||
# 运行应用(默认端口5000)
|
|
||||||
python app.py
|
|
||||||
|
|
||||||
# 开发模式启动(支持热重载)
|
|
||||||
# app.py中默认开启debug=True
|
|
||||||
|
|
||||||
# 生产环境运行
|
|
||||||
# 使用WSGI服务器如gunicorn
|
|
||||||
gunicorn -w 4 -b 0.0.0.0:5000 app:app
|
|
||||||
```
|
|
||||||
|
|
||||||
### 测试和验证
|
|
||||||
```bash
|
|
||||||
# 主要通过Web界面进行功能测试
|
|
||||||
# Cassandra单表查询:http://localhost:5000/db-compare
|
|
||||||
# Redis数据比对:http://localhost:5000/redis-compare
|
|
||||||
# 工具集合首页:http://localhost:5000
|
|
||||||
|
|
||||||
# Cassandra多主键查询测试示例:
|
|
||||||
# 1. 在主键字段中输入:docid,id
|
|
||||||
# 2. 在查询Key值中输入(每行一组):
|
|
||||||
# 8825C293B3609175B2224236E984FEDB,8825C293B3609175B2224236E984FED
|
|
||||||
# 9925C293B3609175B2224236E984FEDB,9925C293B3609175B2224236E984FED
|
|
||||||
|
|
||||||
# Redis查询测试示例:
|
|
||||||
# 1. 配置两个Redis集群连接
|
|
||||||
# 2. 选择随机采样模式,设置采样数量
|
|
||||||
# 3. 或选择指定Key模式,输入要比对的Key列表
|
|
||||||
|
|
||||||
# 数据库初始化(如果config_groups.db不存在)
|
|
||||||
# 通过访问Web界面会自动创建数据库表结构
|
|
||||||
```
|
|
||||||
|
|
||||||
### 依赖项
|
|
||||||
- Flask==2.3.3
|
|
||||||
- cassandra-driver==3.29.1
|
|
||||||
- redis==5.0.1
|
|
||||||
|
|
||||||
### 项目特点
|
|
||||||
- **模块化架构**:清晰的代码组织和职责分离
|
|
||||||
- **双数据源支持**:同时支持Cassandra和Redis数据比对
|
|
||||||
- **智能查询引擎**:针对不同数据源的优化查询策略
|
|
||||||
- **SQLite本地存储**:配置组、查询历史和日志的本地持久化
|
|
||||||
- **前端原生实现**:使用原生JavaScript + Bootstrap,无现代前端框架依赖
|
|
||||||
- **多模式支持**:单表查询、分表查询、多主键查询、Redis比对的统一架构
|
|
||||||
|
|
||||||
## API架构说明
|
|
||||||
|
|
||||||
### Cassandra相关API端点
|
|
||||||
- `GET /api/default-config`: 获取默认Cassandra配置
|
|
||||||
- `POST /api/query`: 执行单表数据库查询比对(支持多主键查询)
|
|
||||||
- `POST /api/sharding-query`: 执行分表查询比对(支持多主键查询)
|
|
||||||
- `GET /api/config-groups`: 获取所有Cassandra配置组
|
|
||||||
- `POST /api/config-groups`: 创建新Cassandra配置组
|
|
||||||
- `GET /api/config-groups/<id>`: 获取特定Cassandra配置组
|
|
||||||
- `DELETE /api/config-groups/<id>`: 删除Cassandra配置组
|
|
||||||
|
|
||||||
### Redis相关API端点
|
|
||||||
- `POST /api/redis/compare`: 执行Redis数据比对
|
|
||||||
- `POST /api/redis/test-connection`: 测试Redis连接
|
|
||||||
- `GET /api/redis/config-groups`: 获取所有Redis配置组
|
|
||||||
- `POST /api/redis/config-groups`: 创建新Redis配置组
|
|
||||||
- `GET /api/redis/config-groups/<id>`: 获取特定Redis配置组
|
|
||||||
- `DELETE /api/redis/config-groups/<id>`: 删除Redis配置组
|
|
||||||
- `GET /api/redis/query-history`: 获取Redis查询历史
|
|
||||||
- `POST /api/redis/query-history`: 保存Redis查询历史
|
|
||||||
- `GET /api/redis/query-history/<id>`: 获取特定Redis历史记录
|
|
||||||
- `DELETE /api/redis/query-history/<id>`: 删除Redis历史记录
|
|
||||||
|
|
||||||
### 通用API端点
|
|
||||||
- `POST /api/init-db`: 初始化SQLite数据库
|
|
||||||
- `GET /api/query-history`: 获取Cassandra查询历史
|
|
||||||
- `POST /api/query-history`: 保存Cassandra查询历史
|
|
||||||
- `GET /api/query-history/<id>`: 获取特定Cassandra历史记录
|
|
||||||
- `GET /api/query-history/<id>/results`: 获取历史记录的完整结果数据
|
|
||||||
- `DELETE /api/query-history/<id>`: 删除Cassandra历史记录
|
|
||||||
- `GET /api/query-logs`: 获取查询日志(支持limit参数)
|
|
||||||
- `GET /api/query-logs/history/<id>`: 获取特定历史记录的相关日志
|
|
||||||
- `DELETE /api/query-logs`: 清空查询日志
|
|
||||||
|
|
||||||
### 查询比对流程
|
|
||||||
|
|
||||||
**Cassandra单表查询流程(`/api/query`)**:
|
|
||||||
1. 前端发送配置和Key值列表到 `/api/query`
|
|
||||||
2. 后端通过 `cassandra_client.py` 创建两个Cassandra连接(生产+测试)
|
|
||||||
3. `query_engine.py` 并行执行查询,获取原始数据
|
|
||||||
4. `data_comparison.py` 运行比较算法,生成差异报告
|
|
||||||
5. 返回完整结果(差异、统计、原始数据)
|
|
||||||
|
|
||||||
**Cassandra分表查询流程(`/api/sharding-query`)**:
|
|
||||||
1. 前端发送配置、Key值列表和分表配置到 `/api/sharding-query`
|
|
||||||
2. 后端使用 `sharding.py` 中的 `ShardingCalculator` 解析Key中的时间戳
|
|
||||||
3. 根据分表算法计算每个Key对应的分表名称
|
|
||||||
4. `query_engine.py` 创建分表映射关系,并行执行分表查询
|
|
||||||
5. 汇总所有分表结果,执行比较算法
|
|
||||||
6. 返回包含分表信息的完整结果
|
|
||||||
|
|
||||||
**Redis数据比对流程(`/api/redis/compare`)**:
|
|
||||||
1. 前端发送Redis集群配置和查询参数到 `/api/redis/compare`
|
|
||||||
2. 后端通过 `redis_client.py` 创建两个Redis连接
|
|
||||||
3. `redis_query.py` 根据查询模式执行数据获取:
|
|
||||||
- 随机采样模式:从源集群随机获取指定数量的Key
|
|
||||||
- 指定Key模式:查询用户提供的Key列表
|
|
||||||
4. 针对每个Key,查询其在两个集群中的值和数据类型
|
|
||||||
5. 执行智能数据比较(根据数据类型选择比较策略)
|
|
||||||
6. 返回比对结果和统计信息
|
|
||||||
|
|
||||||
## 数据结构和配置
|
|
||||||
|
|
||||||
### Cassandra配置结构
|
|
||||||
|
|
||||||
**单表查询配置**:
|
|
||||||
```javascript
|
|
||||||
{
|
|
||||||
pro_config: {
|
|
||||||
cluster_name, datacenter, hosts[], port,
|
|
||||||
username, password, keyspace, table
|
|
||||||
},
|
|
||||||
test_config: { /* 同上 */ },
|
|
||||||
keys: ["主键字段名"], // 支持多个字段,如 ["docid", "id"]
|
|
||||||
fields_to_compare: ["字段1", "字段2"], // 空数组=全部字段
|
|
||||||
exclude_fields: ["排除字段"],
|
|
||||||
values: ["key1", "key2", "key3"] // 单主键或复合主键值
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**分表查询配置**:
|
|
||||||
```javascript
|
|
||||||
{
|
|
||||||
pro_config: { /* 基础配置同上 */ },
|
|
||||||
test_config: { /* 基础配置同上 */ },
|
|
||||||
keys: ["主键字段名"], // 支持复合主键
|
|
||||||
fields_to_compare: ["字段1", "字段2"],
|
|
||||||
exclude_fields: ["排除字段"],
|
|
||||||
values: ["key1", "key2", "key3"], // 支持复合主键值
|
|
||||||
sharding_config: {
|
|
||||||
use_sharding_for_pro: true, // 生产环境是否使用分表
|
|
||||||
use_sharding_for_test: false, // 测试环境是否使用分表
|
|
||||||
interval_seconds: 604800, // 分表时间间隔(默认7天)
|
|
||||||
table_count: 14 // 分表数量(默认14张表)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Redis配置结构
|
|
||||||
|
|
||||||
**Redis集群配置**:
|
|
||||||
```javascript
|
|
||||||
{
|
|
||||||
source_config: {
|
|
||||||
name: "源集群名称",
|
|
||||||
nodes: [
|
|
||||||
{host: "192.168.1.200", port: 7000},
|
|
||||||
{host: "192.168.1.201", port: 7001}
|
|
||||||
],
|
|
||||||
password: "redis_password",
|
|
||||||
socket_timeout: 3,
|
|
||||||
socket_connect_timeout: 3,
|
|
||||||
max_connections_per_node: 16
|
|
||||||
},
|
|
||||||
target_config: { /* 同上 */ },
|
|
||||||
query_config: {
|
|
||||||
mode: "random_sample", // 或 "specific_keys"
|
|
||||||
sample_size: 1000, // 随机采样数量(random_sample模式)
|
|
||||||
keys: ["key1", "key2"], // 指定Key列表(specific_keys模式)
|
|
||||||
key_pattern: "*" // Key匹配模式(可选)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**多主键查询格式示例**:
|
|
||||||
```javascript
|
|
||||||
// 复合主键配置
|
|
||||||
keys: ["docid", "id"]
|
|
||||||
|
|
||||||
// 复合主键查询值(逗号分隔)
|
|
||||||
values: [
|
|
||||||
"8825C293B3609175B2224236E984FEDB,8825C293B3609175B2224236E984FED",
|
|
||||||
"9925C293B3609175B2224236E984FEDB,9925C293B3609175B2224236E984FED"
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 查询结果结构
|
|
||||||
|
|
||||||
**Cassandra查询结果**:
|
|
||||||
```javascript
|
|
||||||
{
|
|
||||||
total_keys, pro_count, test_count,
|
|
||||||
differences: [{
|
|
||||||
key: {docid: "val1", id: "val2"}, // 支持复合主键对象
|
|
||||||
field, pro_value, test_value, message
|
|
||||||
}],
|
|
||||||
identical_results: [{
|
|
||||||
key: {docid: "val1", id: "val2"}, // 支持复合主键对象
|
|
||||||
pro_fields, test_fields
|
|
||||||
}],
|
|
||||||
field_diff_count: { "field_name": count },
|
|
||||||
raw_pro_data: [], raw_test_data: [],
|
|
||||||
summary: { overview, percentages, field_analysis, recommendations },
|
|
||||||
|
|
||||||
// 分表查询特有字段
|
|
||||||
sharding_info: {
|
|
||||||
pro_shard_mapping: { "key1": "table_name_0", "key2": "table_name_1" },
|
|
||||||
test_shard_mapping: { /* 同上 */ },
|
|
||||||
failed_keys: [], // 时间戳提取失败的Key
|
|
||||||
shard_stats: {
|
|
||||||
pro_tables_used: ["table_0", "table_1"],
|
|
||||||
test_tables_used: ["table_0"],
|
|
||||||
timestamp_extraction_success_rate: 95.5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Redis查询结果**:
|
|
||||||
```javascript
|
|
||||||
{
|
|
||||||
total_keys, source_count, target_count,
|
|
||||||
identical_count, different_count, source_only_count, target_only_count,
|
|
||||||
|
|
||||||
// 详细比对结果
|
|
||||||
identical_keys: ["key1", "key2"],
|
|
||||||
different_keys: [
|
|
||||||
{
|
|
||||||
key: "key3",
|
|
||||||
source_type: "string", target_type: "string",
|
|
||||||
source_value: "value1", target_value: "value2",
|
|
||||||
message: "Value mismatch"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
source_only_keys: ["key4"], // 仅源集群存在
|
|
||||||
target_only_keys: ["key5"], // 仅目标集群存在
|
|
||||||
|
|
||||||
// 统计信息
|
|
||||||
type_distribution: {
|
|
||||||
"string": 500, "hash": 200, "list": 100,
|
|
||||||
"set": 50, "zset": 30, "stream": 20
|
|
||||||
},
|
|
||||||
consistency_percentage: 85.5,
|
|
||||||
|
|
||||||
// 性能统计
|
|
||||||
query_time: 2.5,
|
|
||||||
source_connection_time: 0.1,
|
|
||||||
target_connection_time: 0.15
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 开发注意事项
|
|
||||||
|
|
||||||
### 代码修改指导
|
|
||||||
- **模块化开发**:功能按模块组织,修改时注意模块间的依赖关系
|
|
||||||
- **数据库模式变更**:修改SQLite表结构需要考虑向后兼容性
|
|
||||||
- **前端JavaScript**:分别位于 `static/js/app.js`(Cassandra)和 `static/js/redis_compare.js`(Redis)
|
|
||||||
- **HTML模板**:使用Jinja2模板引擎,主要文件在 `templates/` 目录
|
|
||||||
|
|
||||||
### 关键模块和类位置
|
|
||||||
- **主应用**:`app.py` - 应用入口和模块集成
|
|
||||||
- **路由管理**:`modules/api_routes.py` - 所有API端点的实现
|
|
||||||
- **数据库管理**:`modules/database.py` - SQLite数据库操作
|
|
||||||
- **Cassandra客户端**:`modules/cassandra_client.py` - 连接管理和查询执行
|
|
||||||
- **Redis客户端**:`modules/redis_client.py` - Redis连接和性能监控
|
|
||||||
- **查询引擎**:`modules/query_engine.py` - Cassandra查询逻辑
|
|
||||||
- **Redis查询**:`modules/redis_query.py` - Redis数据比对逻辑
|
|
||||||
- **数据比较**:`modules/data_comparison.py` - 智能数据比较算法
|
|
||||||
- **分表计算**:`modules/sharding.py` - TWCS分表逻辑
|
|
||||||
- **配置管理**:`modules/config_manager.py` - 配置组管理
|
|
||||||
- **日志收集**:`modules/query_logger.py` - 查询日志系统
|
|
||||||
|
|
||||||
### 模块依赖关系
|
|
||||||
```
|
|
||||||
app.py
|
|
||||||
├── modules/api_routes.py (路由层)
|
|
||||||
├── modules/config_manager.py (配置管理)
|
|
||||||
├── modules/cassandra_client.py (Cassandra连接)
|
|
||||||
├── modules/redis_client.py (Redis连接)
|
|
||||||
├── modules/query_engine.py (Cassandra查询)
|
|
||||||
│ ├── modules/sharding.py (分表计算)
|
|
||||||
│ └── modules/data_comparison.py (数据比较)
|
|
||||||
├── modules/redis_query.py (Redis查询)
|
|
||||||
└── modules/database.py (SQLite数据库)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 开发最佳实践
|
|
||||||
- **错误处理**:每个模块都有详细的错误分类和处理机制
|
|
||||||
- **日志记录**:使用统一的日志系统,支持不同级别的日志输出
|
|
||||||
- **性能监控**:查询时间和连接时间的详细统计
|
|
||||||
- **配置管理**:支持配置的导入导出和版本管理
|
|
||||||
- **数据安全**:敏感信息(密码)的安全处理
|
|
71
Dockerfile
Normal file
71
Dockerfile
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# BigDataTool Docker镜像
|
||||||
|
# 基于Python 3.9 Alpine镜像构建轻量级容器
|
||||||
|
|
||||||
|
# 使用官方Python 3.9 Alpine镜像作为基础镜像
|
||||||
|
FROM python:3.9-alpine
|
||||||
|
|
||||||
|
# 设置维护者信息
|
||||||
|
LABEL maintainer="BigDataTool Team"
|
||||||
|
LABEL version="2.0"
|
||||||
|
LABEL description="BigDataTool - 大数据查询比对工具容器化版本"
|
||||||
|
|
||||||
|
# 设置环境变量
|
||||||
|
ENV PYTHONUNBUFFERED=1 \
|
||||||
|
PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
PIP_NO_CACHE_DIR=1 \
|
||||||
|
PIP_DISABLE_PIP_VERSION_CHECK=1 \
|
||||||
|
FLASK_HOST=0.0.0.0 \
|
||||||
|
FLASK_PORT=5000
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 安装系统依赖
|
||||||
|
# Alpine需要的构建工具和运行时库
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
gcc \
|
||||||
|
musl-dev \
|
||||||
|
libffi-dev \
|
||||||
|
openssl-dev \
|
||||||
|
cargo \
|
||||||
|
rust \
|
||||||
|
&& apk add --no-cache --virtual .build-deps \
|
||||||
|
build-base \
|
||||||
|
python3-dev
|
||||||
|
|
||||||
|
# 复制requirements文件
|
||||||
|
COPY requirements.txt .
|
||||||
|
|
||||||
|
# 安装Python依赖
|
||||||
|
# 使用国内镜像源加速下载
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple/
|
||||||
|
|
||||||
|
# 清理构建依赖以减小镜像大小
|
||||||
|
RUN apk del .build-deps
|
||||||
|
|
||||||
|
# 复制应用代码
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# 创建必要的目录
|
||||||
|
RUN mkdir -p logs && \
|
||||||
|
chmod +x docker-entrypoint.sh || true
|
||||||
|
|
||||||
|
# 创建非root用户运行应用
|
||||||
|
RUN addgroup -g 1001 -S appgroup && \
|
||||||
|
adduser -u 1001 -S appuser -G appgroup
|
||||||
|
|
||||||
|
# 更改文件所有权
|
||||||
|
RUN chown -R appuser:appgroup /app
|
||||||
|
|
||||||
|
# 切换到非root用户
|
||||||
|
USER appuser
|
||||||
|
|
||||||
|
# 暴露端口
|
||||||
|
EXPOSE 5000
|
||||||
|
|
||||||
|
# 健康检查
|
||||||
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||||
|
CMD wget --no-verbose --tries=1 --spider http://localhost:5000/api/health || exit 1
|
||||||
|
|
||||||
|
# 设置启动命令
|
||||||
|
CMD ["python", "app.py"]
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 BigDataTool项目组
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
148
Makefile
Normal file
148
Makefile
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
# BigDataTool Docker 管理 Makefile
|
||||||
|
.PHONY: help build run stop clean logs shell health
|
||||||
|
|
||||||
|
# 默认目标
|
||||||
|
help:
|
||||||
|
@echo "BigDataTool Docker 管理命令:"
|
||||||
|
@echo ""
|
||||||
|
@echo " build 构建Docker镜像"
|
||||||
|
@echo " run 启动服务(简化版本)"
|
||||||
|
@echo " run-full 启动完整服务(包含缓存和监控)"
|
||||||
|
@echo " stop 停止服务"
|
||||||
|
@echo " restart 重启服务"
|
||||||
|
@echo " clean 清理容器和镜像"
|
||||||
|
@echo " logs 查看服务日志"
|
||||||
|
@echo " shell 进入容器shell"
|
||||||
|
@echo " health 检查服务健康状态"
|
||||||
|
@echo " ps 查看运行状态"
|
||||||
|
@echo ""
|
||||||
|
@echo "环境变量设置:"
|
||||||
|
@echo " export SECRET_KEY=your-secret-key"
|
||||||
|
@echo " export FLASK_ENV=production"
|
||||||
|
@echo ""
|
||||||
|
|
||||||
|
# 构建镜像
|
||||||
|
build:
|
||||||
|
@echo "构建BigDataTool Docker镜像..."
|
||||||
|
docker build -t bigdatatool:latest .
|
||||||
|
|
||||||
|
# 快速运行(简化版本)
|
||||||
|
run:
|
||||||
|
@echo "启动BigDataTool服务(简化版本)..."
|
||||||
|
docker-compose -f docker-compose.simple.yml up -d
|
||||||
|
@echo "服务启动中,请等待30秒后访问 http://localhost:5000"
|
||||||
|
|
||||||
|
# 完整运行(包含缓存和监控)
|
||||||
|
run-full:
|
||||||
|
@echo "启动BigDataTool完整服务..."
|
||||||
|
docker-compose up -d
|
||||||
|
@echo "服务启动中,请等待30秒后访问:"
|
||||||
|
@echo " - 主应用: http://localhost:5000"
|
||||||
|
@echo " - Redis缓存: localhost:6379"
|
||||||
|
|
||||||
|
# 生产环境运行(包含Nginx)
|
||||||
|
run-prod:
|
||||||
|
@echo "启动生产环境服务..."
|
||||||
|
docker-compose --profile production up -d
|
||||||
|
@echo "生产环境服务启动,访问地址:"
|
||||||
|
@echo " - HTTP: http://localhost"
|
||||||
|
@echo " - HTTPS: https://localhost (需要SSL证书)"
|
||||||
|
|
||||||
|
# 监控环境运行
|
||||||
|
run-monitor:
|
||||||
|
@echo "启动监控环境..."
|
||||||
|
docker-compose --profile monitoring up -d
|
||||||
|
@echo "监控服务启动,访问地址:"
|
||||||
|
@echo " - 主应用: http://localhost:5000"
|
||||||
|
@echo " - Prometheus: http://localhost:9090"
|
||||||
|
|
||||||
|
# 停止服务
|
||||||
|
stop:
|
||||||
|
@echo "停止所有服务..."
|
||||||
|
docker-compose down
|
||||||
|
docker-compose -f docker-compose.simple.yml down
|
||||||
|
|
||||||
|
# 重启服务
|
||||||
|
restart: stop run
|
||||||
|
|
||||||
|
# 查看日志
|
||||||
|
logs:
|
||||||
|
@echo "查看服务日志..."
|
||||||
|
docker-compose logs -f bigdatatool
|
||||||
|
|
||||||
|
# 查看特定服务日志
|
||||||
|
logs-app:
|
||||||
|
docker-compose logs -f bigdatatool
|
||||||
|
|
||||||
|
logs-redis:
|
||||||
|
docker-compose logs -f redis-cache
|
||||||
|
|
||||||
|
logs-nginx:
|
||||||
|
docker-compose logs -f nginx
|
||||||
|
|
||||||
|
# 进入容器shell
|
||||||
|
shell:
|
||||||
|
@echo "进入BigDataTool容器..."
|
||||||
|
docker-compose exec bigdatatool /bin/bash
|
||||||
|
|
||||||
|
# 健康检查
|
||||||
|
health:
|
||||||
|
@echo "检查服务健康状态..."
|
||||||
|
@docker-compose ps
|
||||||
|
@echo ""
|
||||||
|
@echo "应用健康检查:"
|
||||||
|
@curl -s http://localhost:5000/api/health | python -m json.tool || echo "服务未响应"
|
||||||
|
|
||||||
|
# 查看运行状态
|
||||||
|
ps:
|
||||||
|
@echo "容器运行状态:"
|
||||||
|
@docker-compose ps
|
||||||
|
|
||||||
|
# 清理资源
|
||||||
|
clean:
|
||||||
|
@echo "清理Docker资源..."
|
||||||
|
docker-compose down -v --remove-orphans
|
||||||
|
docker-compose -f docker-compose.simple.yml down -v --remove-orphans
|
||||||
|
docker system prune -f
|
||||||
|
@echo "清理完成"
|
||||||
|
|
||||||
|
# 强制清理(包括镜像)
|
||||||
|
clean-all: clean
|
||||||
|
@echo "强制清理所有资源..."
|
||||||
|
docker rmi bigdatatool:latest || true
|
||||||
|
docker volume prune -f
|
||||||
|
docker network prune -f
|
||||||
|
|
||||||
|
# 更新镜像
|
||||||
|
update: clean build run
|
||||||
|
|
||||||
|
# 查看资源使用
|
||||||
|
stats:
|
||||||
|
@echo "Docker资源使用情况:"
|
||||||
|
@docker stats --no-stream
|
||||||
|
|
||||||
|
# 备份数据
|
||||||
|
backup:
|
||||||
|
@echo "备份数据库和配置..."
|
||||||
|
@mkdir -p backups/$(shell date +%Y%m%d_%H%M%S)
|
||||||
|
@docker cp bigdatatool:/app/config_groups.db backups/$(shell date +%Y%m%d_%H%M%S)/
|
||||||
|
@echo "备份完成: backups/$(shell date +%Y%m%d_%H%M%S)/"
|
||||||
|
|
||||||
|
# 开发模式运行
|
||||||
|
dev:
|
||||||
|
@echo "开发模式运行..."
|
||||||
|
@docker run --rm -it \
|
||||||
|
-p 5000:5000 \
|
||||||
|
-v $(PWD):/app \
|
||||||
|
-e FLASK_ENV=development \
|
||||||
|
-e FLASK_DEBUG=True \
|
||||||
|
bigdatatool:latest
|
||||||
|
|
||||||
|
# 构建并推送到仓库(需要先登录Docker Hub)
|
||||||
|
publish: build
|
||||||
|
@echo "推送镜像到Docker Hub..."
|
||||||
|
@read -p "请输入Docker Hub用户名: " username && \
|
||||||
|
docker tag bigdatatool:latest $$username/bigdatatool:latest && \
|
||||||
|
docker tag bigdatatool:latest $$username/bigdatatool:2.0 && \
|
||||||
|
docker push $$username/bigdatatool:latest && \
|
||||||
|
docker push $$username/bigdatatool:2.0
|
365
README.md
365
README.md
@@ -35,23 +35,141 @@ BigDataTool是一个功能强大的数据库查询比对工具,专门用于Cas
|
|||||||
|
|
||||||
## 🛠️ 安装部署
|
## 🛠️ 安装部署
|
||||||
|
|
||||||
### 1. 克隆项目
|
### 快速开始
|
||||||
|
|
||||||
|
#### 方式1:直接运行(推荐开发环境)
|
||||||
```bash
|
```bash
|
||||||
|
# 1. 克隆项目
|
||||||
git clone https://github.com/your-org/BigDataTool.git
|
git clone https://github.com/your-org/BigDataTool.git
|
||||||
cd BigDataTool
|
cd BigDataTool
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 安装依赖
|
# 2. 安装依赖
|
||||||
```bash
|
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 启动应用
|
# 3. 启动应用
|
||||||
```bash
|
|
||||||
python app.py
|
python app.py
|
||||||
```
|
```
|
||||||
|
|
||||||
应用将在 `http://localhost:5000` 启动
|
#### 方式2:Docker容器化部署(推荐生产环境)
|
||||||
|
```bash
|
||||||
|
# 1. 克隆项目
|
||||||
|
git clone https://github.com/your-org/BigDataTool.git
|
||||||
|
cd BigDataTool
|
||||||
|
|
||||||
|
# 2. 构建并启动(简化版本)
|
||||||
|
make build
|
||||||
|
make run
|
||||||
|
|
||||||
|
# 或者使用Docker Compose直接启动
|
||||||
|
docker-compose -f docker-compose.simple.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 方式3:完整Docker环境(包含缓存和监控)
|
||||||
|
```bash
|
||||||
|
# 启动完整服务栈
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# 查看服务状态
|
||||||
|
make ps
|
||||||
|
```
|
||||||
|
|
||||||
|
### 容器化部署详情
|
||||||
|
|
||||||
|
#### 🐳 Docker镜像特性
|
||||||
|
- **基础镜像**: Python 3.9 Alpine(轻量级)
|
||||||
|
- **镜像大小**: < 200MB
|
||||||
|
- **安全性**: 非root用户运行
|
||||||
|
- **健康检查**: 内置应用健康监控
|
||||||
|
- **多架构**: 支持AMD64和ARM64
|
||||||
|
|
||||||
|
#### 🚀 一键部署命令
|
||||||
|
```bash
|
||||||
|
# 查看所有可用命令
|
||||||
|
make help
|
||||||
|
|
||||||
|
# 构建镜像
|
||||||
|
make build
|
||||||
|
|
||||||
|
# 启动服务(简化版本)
|
||||||
|
make run
|
||||||
|
|
||||||
|
# 启动完整服务(包含Redis缓存)
|
||||||
|
make run-full
|
||||||
|
|
||||||
|
# 启动生产环境(包含Nginx反向代理)
|
||||||
|
make run-prod
|
||||||
|
|
||||||
|
# 查看服务日志
|
||||||
|
make logs
|
||||||
|
|
||||||
|
# 进入容器调试
|
||||||
|
make shell
|
||||||
|
|
||||||
|
# 健康检查
|
||||||
|
make health
|
||||||
|
|
||||||
|
# 停止服务
|
||||||
|
make stop
|
||||||
|
|
||||||
|
# 清理资源
|
||||||
|
make clean
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 🔧 环境变量配置
|
||||||
|
```bash
|
||||||
|
# 设置应用密钥(生产环境必须设置)
|
||||||
|
export SECRET_KEY="your-super-secret-key-change-in-production"
|
||||||
|
|
||||||
|
# 设置运行环境
|
||||||
|
export FLASK_ENV=production
|
||||||
|
export FLASK_DEBUG=False
|
||||||
|
|
||||||
|
# 数据库配置
|
||||||
|
export DATABASE_URL="sqlite:///config_groups.db"
|
||||||
|
|
||||||
|
# 安全配置
|
||||||
|
export FORCE_HTTPS=true
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 📊 服务端点
|
||||||
|
启动后可访问以下地址:
|
||||||
|
|
||||||
|
**简化部署**:
|
||||||
|
- 主应用: http://localhost:5000
|
||||||
|
|
||||||
|
**完整部署**:
|
||||||
|
- 主应用: http://localhost:5000
|
||||||
|
- Redis缓存: localhost:6379
|
||||||
|
- Prometheus监控: http://localhost:9090
|
||||||
|
|
||||||
|
**生产环境**:
|
||||||
|
- HTTP: http://localhost
|
||||||
|
- HTTPS: https://localhost
|
||||||
|
|
||||||
|
### 传统部署方式
|
||||||
|
|
||||||
|
#### Python虚拟环境部署
|
||||||
|
```bash
|
||||||
|
# 创建虚拟环境
|
||||||
|
python -m venv venv
|
||||||
|
source venv/bin/activate # Linux/Mac
|
||||||
|
# 或 venv\Scripts\activate # Windows
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
# 启动应用
|
||||||
|
python app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 生产环境部署(Gunicorn)
|
||||||
|
```bash
|
||||||
|
# 安装Gunicorn
|
||||||
|
pip install gunicorn
|
||||||
|
|
||||||
|
# 启动生产服务
|
||||||
|
gunicorn -w 4 -b 0.0.0.0:5000 app:app
|
||||||
|
```
|
||||||
|
|
||||||
## 🎯 快速开始
|
## 🎯 快速开始
|
||||||
|
|
||||||
@@ -88,7 +206,51 @@ python app.py
|
|||||||
4. 设置查询参数
|
4. 设置查询参数
|
||||||
5. 执行比对分析
|
5. 执行比对分析
|
||||||
|
|
||||||
## 📊 功能特性
|
## 🏗️ 系统架构
|
||||||
|
|
||||||
|
BigDataTool采用模块化分层架构设计:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ 前端界面层 │
|
||||||
|
│ (HTML + JavaScript + Bootstrap) │
|
||||||
|
└─────────────┬───────────────────────────┘
|
||||||
|
│
|
||||||
|
┌─────────────▼───────────────────────────┐
|
||||||
|
│ API路由层 │
|
||||||
|
│ (Flask Routes) │
|
||||||
|
└─────────────┬───────────────────────────┘
|
||||||
|
│
|
||||||
|
┌─────────────▼───────────────────────────┐
|
||||||
|
│ 业务逻辑层 │
|
||||||
|
│ ┌─────────────┬─────────────────┐ │
|
||||||
|
│ │ 查询引擎 │ 比对引擎 │ │
|
||||||
|
│ │Query Engine │ Comparison │ │
|
||||||
|
│ └─────────────┴─────────────────┘ │
|
||||||
|
└─────────────┬───────────────────────────┘
|
||||||
|
│
|
||||||
|
┌─────────────▼───────────────────────────┐
|
||||||
|
│ 数据访问层 │
|
||||||
|
│ ┌─────────────┬─────────────────┐ │
|
||||||
|
│ │ Cassandra │ Redis │ │
|
||||||
|
│ │ Client │ Client │ │
|
||||||
|
│ └─────────────┴─────────────────┘ │
|
||||||
|
└─────────────┬───────────────────────────┘
|
||||||
|
│
|
||||||
|
┌─────────────▼───────────────────────────┐
|
||||||
|
│ 数据存储层 │
|
||||||
|
│ ┌──────┬──────┬─────────────────┐ │
|
||||||
|
│ │SQLite│Cassandra│ Redis │ │
|
||||||
|
│ │(配置) │ (生产) │ (缓存) │ │
|
||||||
|
│ └──────┴──────┴─────────────────┘ │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 核心组件
|
||||||
|
- **查询引擎**: 负责Cassandra和Redis的查询执行
|
||||||
|
- **比对引擎**: 实现智能数据比对算法
|
||||||
|
- **配置管理**: SQLite存储的配置持久化
|
||||||
|
- **日志系统**: 实时查询日志收集和展示
|
||||||
|
|
||||||
### 数据比对引擎
|
### 数据比对引擎
|
||||||
- **智能JSON比较**:自动处理JSON格式差异和嵌套结构
|
- **智能JSON比较**:自动处理JSON格式差异和嵌套结构
|
||||||
@@ -113,24 +275,24 @@ python app.py
|
|||||||
### Cassandra配置
|
### Cassandra配置
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"cluster_name": "生产集群",
|
"cluster_name": "示例集群",
|
||||||
"hosts": ["192.168.1.100", "192.168.1.101"],
|
"hosts": ["127.0.0.1", "127.0.0.2"],
|
||||||
"port": 9042,
|
"port": 9042,
|
||||||
"datacenter": "dc1",
|
"datacenter": "dc1",
|
||||||
"username": "cassandra",
|
"username": "cassandra",
|
||||||
"password": "password",
|
"password": "password",
|
||||||
"keyspace": "my_keyspace",
|
"keyspace": "example_keyspace",
|
||||||
"table": "my_table"
|
"table": "example_table"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Redis配置
|
### Redis配置
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"name": "生产Redis",
|
"name": "示例Redis",
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{"host": "192.168.1.200", "port": 7000},
|
{"host": "127.0.0.1", "port": 6379},
|
||||||
{"host": "192.168.1.201", "port": 7001}
|
{"host": "127.0.0.2", "port": 6379}
|
||||||
],
|
],
|
||||||
"password": "redis_password",
|
"password": "redis_password",
|
||||||
"socket_timeout": 3,
|
"socket_timeout": 3,
|
||||||
@@ -139,13 +301,40 @@ python app.py
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📈 性能优化
|
## 📈 性能指标
|
||||||
|
|
||||||
- **连接池管理**:优化的数据库连接复用
|
### 响应时间
|
||||||
- **批量查询**:支持大批量Key的高效查询
|
- 单表查询(100条记录):< 10秒
|
||||||
- **内存管理**:大结果集的内存友好处理
|
- 分表查询(100条记录):< 15秒
|
||||||
- **并行处理**:多表并行查询和数据比对
|
- Redis查询(100个Key):< 10秒
|
||||||
- **缓存机制**:查询结果和配置的智能缓存
|
- 页面加载时间:< 3秒
|
||||||
|
|
||||||
|
### 系统容量
|
||||||
|
- 最大并发查询数:10个
|
||||||
|
- 单次最大查询记录:10,000条
|
||||||
|
- 支持的数据库连接数:无限制
|
||||||
|
- 内存使用峰值:< 1GB
|
||||||
|
|
||||||
|
### 数据处理能力
|
||||||
|
- Cassandra分表自动计算准确率:> 95%
|
||||||
|
- JSON深度比较支持嵌套层级:无限制
|
||||||
|
- Redis全数据类型支持:100%
|
||||||
|
- 查询历史存储容量:无限制
|
||||||
|
|
||||||
|
## 🔄 版本更新
|
||||||
|
|
||||||
|
### v2.0 (2024-08)
|
||||||
|
- ✨ 新增Redis集群比对功能
|
||||||
|
- ✨ 支持多主键复合查询
|
||||||
|
- ✨ 智能数据类型检测和比对
|
||||||
|
- 🚀 性能优化和UI改进
|
||||||
|
- 📚 完整文档体系建设
|
||||||
|
|
||||||
|
### v1.0 (2024-07)
|
||||||
|
- 🎉 基础Cassandra数据比对功能
|
||||||
|
- 🎉 TWCS分表查询支持
|
||||||
|
- 🎉 配置管理和查询历史
|
||||||
|
- 🎉 Web界面和API接口
|
||||||
|
|
||||||
## 🔍 故障排查
|
## 🔍 故障排查
|
||||||
|
|
||||||
@@ -166,6 +355,17 @@ python app.py
|
|||||||
- 检查数据库服务器负载
|
- 检查数据库服务器负载
|
||||||
- 优化查询条件和索引
|
- 优化查询条件和索引
|
||||||
|
|
||||||
|
4. **内存使用过高**
|
||||||
|
- 减少单次查询的记录数量
|
||||||
|
- 使用分批查询处理大数据集
|
||||||
|
- 定期清理查询历史和日志
|
||||||
|
|
||||||
|
5. **分表查询失败**
|
||||||
|
- 检查Key中是否包含有效时间戳
|
||||||
|
- 确认分表参数配置正确
|
||||||
|
- 验证目标分表是否存在
|
||||||
|
|
||||||
|
|
||||||
## 📝 API文档
|
## 📝 API文档
|
||||||
|
|
||||||
### 主要API端点
|
### 主要API端点
|
||||||
@@ -178,35 +378,120 @@ python app.py
|
|||||||
- `GET /api/query-history` - 获取查询历史
|
- `GET /api/query-history` - 获取查询历史
|
||||||
- `GET /api/query-logs` - 获取查询日志
|
- `GET /api/query-logs` - 获取查询日志
|
||||||
|
|
||||||
详细API文档请参考 [API.md](docs/API.md)
|
|
||||||
|
|
||||||
## 📚 文档目录
|
|
||||||
|
|
||||||
- [API文档](docs/API.md) - 完整的API接口说明
|
|
||||||
- [使用指南](docs/USER_GUIDE.md) - 详细的功能使用说明
|
|
||||||
- [架构设计](docs/ARCHITECTURE.md) - 系统架构和设计原理
|
|
||||||
- [部署指南](docs/DEPLOYMENT.md) - 生产环境部署说明
|
|
||||||
|
|
||||||
## 🤝 贡献指南
|
## 🤝 贡献指南
|
||||||
|
|
||||||
1. Fork 项目
|
我们欢迎所有形式的贡献!请遵循以下步骤:
|
||||||
2. 创建功能分支 (`git checkout -b feature/AmazingFeature`)
|
|
||||||
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
|
### 基本流程
|
||||||
4. 推送到分支 (`git push origin feature/AmazingFeature`)
|
1. **Fork项目**
|
||||||
5. 开启 Pull Request
|
```bash
|
||||||
|
git clone https://github.com/your-username/BigDataTool.git
|
||||||
|
cd BigDataTool
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **创建功能分支**
|
||||||
|
```bash
|
||||||
|
git checkout -b feature/amazing-feature
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **遵循代码规范**
|
||||||
|
- 查看 [代码规范](docs/coding-standards.md)
|
||||||
|
- 使用PEP 8 Python风格
|
||||||
|
- 添加必要的测试用例
|
||||||
|
- 更新相关文档
|
||||||
|
|
||||||
|
4. **提交更改**
|
||||||
|
```bash
|
||||||
|
git commit -m 'feat: Add some AmazingFeature'
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **推送到分支**
|
||||||
|
```bash
|
||||||
|
git push origin feature/amazing-feature
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **创建Pull Request**
|
||||||
|
- 描述变更内容和原因
|
||||||
|
- 确保所有测试通过
|
||||||
|
- 添加必要的截图或演示
|
||||||
|
|
||||||
|
### 贡献类型
|
||||||
|
- 🐛 Bug修复
|
||||||
|
- ✨ 新功能开发
|
||||||
|
- 📚 文档改进
|
||||||
|
- 🎨 界面优化
|
||||||
|
- 🚀 性能优化
|
||||||
|
- 🔧 配置和工具
|
||||||
|
|
||||||
|
### 代码审查
|
||||||
|
所有贡献都将经过代码审查,包括:
|
||||||
|
- 功能正确性验证
|
||||||
|
- 代码质量检查
|
||||||
|
- 安全性评估
|
||||||
|
- 文档完整性确认
|
||||||
|
|
||||||
|
详细开发指南请参考 [开发者文档](docs/developer-guide.md)
|
||||||
|
|
||||||
|
## 🛡️ 安全声明
|
||||||
|
|
||||||
|
BigDataTool致力于数据安全:
|
||||||
|
|
||||||
|
- 🔒 **传输加密**: 支持HTTPS/TLS加密传输
|
||||||
|
- 🔐 **认证机制**: 预留身份认证和权限控制接口
|
||||||
|
- 🔍 **输入验证**: 严格的输入参数验证和过滤
|
||||||
|
- 📝 **审计日志**: 完整的操作日志和安全事件记录
|
||||||
|
- 🛡️ **数据保护**: 敏感信息不明文存储
|
||||||
|
|
||||||
|
如发现安全漏洞,请发送邮件至安全团队或创建私密Issue。
|
||||||
|
|
||||||
|
详细安全规范请参考 [安全指南](docs/security-guidelines.md)
|
||||||
|
|
||||||
## 📄 许可证
|
## 📄 许可证
|
||||||
|
|
||||||
本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情
|
本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情
|
||||||
|
|
||||||
## 👥 作者
|
## 👥 项目团队
|
||||||
|
|
||||||
BigDataTool项目组
|
### 核心开发者
|
||||||
|
- **项目负责人**: BigDataTool项目组
|
||||||
|
- **架构师**: 系统架构设计团队
|
||||||
|
- **前端开发**: UI/UX开发团队
|
||||||
|
- **后端开发**: 数据处理引擎团队
|
||||||
|
- **测试工程师**: 质量保证团队
|
||||||
|
|
||||||
|
### 贡献者统计
|
||||||
|
感谢所有为项目做出贡献的开发者!
|
||||||
|
|
||||||
|
## 📞 支持与反馈
|
||||||
|
|
||||||
|
### 问题报告
|
||||||
|
- 🐛 [Bug报告](https://github.com/your-org/BigDataTool/issues/new?template=bug_report.md)
|
||||||
|
- ✨ [功能请求](https://github.com/your-org/BigDataTool/issues/new?template=feature_request.md)
|
||||||
|
- ❓ [问题讨论](https://github.com/your-org/BigDataTool/discussions)
|
||||||
|
|
||||||
|
### 社区支持
|
||||||
|
- 📚 查看 [用户手册](docs/user-manual.md) 获取详细使用说明
|
||||||
|
- 🔧 查看 [故障排查指南](docs/operations.md) 解决常见问题
|
||||||
|
- 💬 加入社区讨论组获取实时帮助
|
||||||
|
|
||||||
## 🙏 致谢
|
## 🙏 致谢
|
||||||
|
|
||||||
感谢所有为这个项目做出贡献的开发者和用户。
|
感谢以下开源项目和技术社区的支持:
|
||||||
|
|
||||||
|
- **[Flask](https://flask.palletsprojects.com/)** - 轻量级Web框架
|
||||||
|
- **[Cassandra](https://cassandra.apache.org/)** - 分布式NoSQL数据库
|
||||||
|
- **[Redis](https://redis.io/)** - 高性能键值存储
|
||||||
|
- **[Bootstrap](https://getbootstrap.com/)** - 前端UI框架
|
||||||
|
- **[jQuery](https://jquery.com/)** - JavaScript库
|
||||||
|
|
||||||
|
特别感谢所有提供反馈、bug报告和功能建议的用户!
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**注意**:使用前请确保已正确配置数据库连接信息,并在生产环境中谨慎使用。
|
## 📊 项目状态
|
||||||
|
|
||||||
|
**最后更新**: 2024年8月6日
|
||||||
|
**当前版本**: v2.0
|
||||||
|
**开发状态**: 持续维护中
|
||||||
|
|
||||||
|
> ⚠️ **重要提示**: 本工具主要用于开发测试环境的数据比对,生产环境使用请谨慎评估并做好安全防护。建议在使用前详细阅读 [安全指南](docs/security-guidelines.md)。
|
||||||
|
13
app.py
13
app.py
@@ -76,6 +76,15 @@ if __name__ == '__main__':
|
|||||||
logger.info("=== BigDataTool 启动 ===")
|
logger.info("=== BigDataTool 启动 ===")
|
||||||
logger.info("应用架构:模块化")
|
logger.info("应用架构:模块化")
|
||||||
logger.info("支持功能:单表查询、分表查询、多主键查询、配置管理、查询历史")
|
logger.info("支持功能:单表查询、分表查询、多主键查询、配置管理、查询历史")
|
||||||
logger.info("访问地址:http://localhost:5000")
|
|
||||||
|
# 从环境变量获取配置,支持Docker部署
|
||||||
|
import os
|
||||||
|
host = os.getenv('FLASK_HOST', '0.0.0.0')
|
||||||
|
port = int(os.getenv('FLASK_PORT', 5000))
|
||||||
|
debug = os.getenv('FLASK_DEBUG', 'True').lower() == 'true'
|
||||||
|
|
||||||
|
logger.info(f"访问地址:http://{host}:{port}")
|
||||||
logger.info("API文档:/api/* 路径下的所有端点")
|
logger.info("API文档:/api/* 路径下的所有端点")
|
||||||
app.run(debug=True, port=5000)
|
logger.info(f"配置信息 - 主机: {host}, 端口: {port}, 调试: {debug}")
|
||||||
|
|
||||||
|
app.run(debug=debug, host=host, port=port)
|
29
docker-compose.simple.yml
Normal file
29
docker-compose.simple.yml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# BigDataTool 简化版本 Docker Compose 配置
|
||||||
|
# 适用于快速开发和测试
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
bigdatatool:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: bigdatatool
|
||||||
|
ports:
|
||||||
|
- "8080:5000"
|
||||||
|
environment:
|
||||||
|
- FLASK_ENV=production
|
||||||
|
- FLASK_DEBUG=False
|
||||||
|
- FLASK_HOST=0.0.0.0
|
||||||
|
- FLASK_PORT=5000
|
||||||
|
# volumes:
|
||||||
|
# # 持久化数据库
|
||||||
|
# - ./data:/app/data
|
||||||
|
# # 持久化日志
|
||||||
|
# - ./logs:/app/logs
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5000/api/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 40s
|
237
docker-entrypoint.sh
Executable file
237
docker-entrypoint.sh
Executable file
@@ -0,0 +1,237 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# BigDataTool Docker 启动脚本
|
||||||
|
# 用于容器化部署的入口脚本
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# 颜色定义
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# 日志函数
|
||||||
|
log_info() {
|
||||||
|
echo -e "${BLUE}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warn() {
|
||||||
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_success() {
|
||||||
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 显示启动信息
|
||||||
|
show_banner() {
|
||||||
|
echo "======================================"
|
||||||
|
echo " BigDataTool Container Startup"
|
||||||
|
echo "======================================"
|
||||||
|
echo "Version: 2.0"
|
||||||
|
echo "Python: $(python --version)"
|
||||||
|
echo "Working Directory: $(pwd)"
|
||||||
|
echo "User: $(whoami)"
|
||||||
|
echo "======================================"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 检查环境变量
|
||||||
|
check_environment() {
|
||||||
|
log_info "检查环境变量..."
|
||||||
|
|
||||||
|
# 设置默认值
|
||||||
|
export FLASK_ENV=${FLASK_ENV:-production}
|
||||||
|
export FLASK_DEBUG=${FLASK_DEBUG:-False}
|
||||||
|
export SECRET_KEY=${SECRET_KEY:-$(python -c "import secrets; print(secrets.token_hex(32))")}
|
||||||
|
export DATABASE_URL=${DATABASE_URL:-sqlite:///config_groups.db}
|
||||||
|
|
||||||
|
log_info "FLASK_ENV: $FLASK_ENV"
|
||||||
|
log_info "FLASK_DEBUG: $FLASK_DEBUG"
|
||||||
|
log_info "数据库URL: $DATABASE_URL"
|
||||||
|
|
||||||
|
# 检查必要的环境变量
|
||||||
|
if [ -z "$SECRET_KEY" ]; then
|
||||||
|
log_warn "SECRET_KEY 未设置,使用随机生成的密钥"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 初始化数据库
|
||||||
|
initialize_database() {
|
||||||
|
log_info "初始化数据库..."
|
||||||
|
|
||||||
|
# 检查数据库文件是否存在
|
||||||
|
if [ ! -f "config_groups.db" ]; then
|
||||||
|
log_info "数据库文件不存在,将自动创建"
|
||||||
|
python -c "
|
||||||
|
from modules.database import ensure_database
|
||||||
|
if ensure_database():
|
||||||
|
print('数据库初始化成功')
|
||||||
|
else:
|
||||||
|
print('数据库初始化失败')
|
||||||
|
exit(1)
|
||||||
|
"
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
log_success "数据库初始化完成"
|
||||||
|
else
|
||||||
|
log_error "数据库初始化失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_info "数据库文件已存在"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 创建必要目录
|
||||||
|
create_directories() {
|
||||||
|
log_info "创建必要目录..."
|
||||||
|
|
||||||
|
# 创建日志目录
|
||||||
|
if [ ! -d "logs" ]; then
|
||||||
|
mkdir -p logs
|
||||||
|
log_info "创建日志目录: logs"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 创建配置目录
|
||||||
|
if [ ! -d "config" ]; then
|
||||||
|
mkdir -p config
|
||||||
|
log_info "创建配置目录: config"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 设置权限
|
||||||
|
chmod -R 755 logs config || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# 健康检查函数
|
||||||
|
health_check() {
|
||||||
|
log_info "执行健康检查..."
|
||||||
|
|
||||||
|
local max_attempts=30
|
||||||
|
local attempt=1
|
||||||
|
|
||||||
|
while [ $attempt -le $max_attempts ]; do
|
||||||
|
if wget --no-verbose --tries=1 --spider http://localhost:5000/api/health >/dev/null 2>&1; then
|
||||||
|
log_success "应用健康检查通过"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "等待应用启动... ($attempt/$max_attempts)"
|
||||||
|
sleep 2
|
||||||
|
((attempt++))
|
||||||
|
done
|
||||||
|
|
||||||
|
log_error "健康检查失败,应用可能未正常启动"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 信号处理函数
|
||||||
|
cleanup() {
|
||||||
|
log_warn "收到退出信号,正在清理..."
|
||||||
|
|
||||||
|
# 这里可以添加清理逻辑
|
||||||
|
# 例如:保存缓存、关闭数据库连接等
|
||||||
|
|
||||||
|
log_info "清理完成,退出应用"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# 设置信号处理
|
||||||
|
trap cleanup SIGTERM SIGINT
|
||||||
|
|
||||||
|
# 主启动函数
|
||||||
|
start_application() {
|
||||||
|
log_info "启动BigDataTool应用..."
|
||||||
|
|
||||||
|
# 根据环境变量选择启动方式
|
||||||
|
if [ "$FLASK_ENV" = "development" ]; then
|
||||||
|
log_info "以开发模式启动"
|
||||||
|
python app.py
|
||||||
|
else
|
||||||
|
log_info "以生产模式启动"
|
||||||
|
|
||||||
|
# 检查是否安装了gunicorn
|
||||||
|
if command -v gunicorn >/dev/null 2>&1; then
|
||||||
|
log_info "使用Gunicorn启动应用"
|
||||||
|
exec gunicorn \
|
||||||
|
--bind 0.0.0.0:5000 \
|
||||||
|
--workers 4 \
|
||||||
|
--worker-class sync \
|
||||||
|
--worker-connections 1000 \
|
||||||
|
--max-requests 1000 \
|
||||||
|
--max-requests-jitter 50 \
|
||||||
|
--timeout 120 \
|
||||||
|
--keep-alive 5 \
|
||||||
|
--log-level info \
|
||||||
|
--access-logfile - \
|
||||||
|
--error-logfile - \
|
||||||
|
app:app
|
||||||
|
else
|
||||||
|
log_warn "Gunicorn未安装,使用Flask开发服务器"
|
||||||
|
python app.py
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 显示帮助信息
|
||||||
|
show_help() {
|
||||||
|
echo "BigDataTool Docker 启动脚本"
|
||||||
|
echo ""
|
||||||
|
echo "用法: $0 [选项]"
|
||||||
|
echo ""
|
||||||
|
echo "选项:"
|
||||||
|
echo " start 启动应用(默认)"
|
||||||
|
echo " health-check 执行健康检查"
|
||||||
|
echo " init-db 仅初始化数据库"
|
||||||
|
echo " shell 进入交互式shell"
|
||||||
|
echo " help 显示此帮助信息"
|
||||||
|
echo ""
|
||||||
|
echo "环境变量:"
|
||||||
|
echo " FLASK_ENV Flask运行环境 (development/production)"
|
||||||
|
echo " FLASK_DEBUG 是否启用调试模式 (True/False)"
|
||||||
|
echo " SECRET_KEY 应用密钥"
|
||||||
|
echo " DATABASE_URL 数据库连接URL"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# 主逻辑
|
||||||
|
main() {
|
||||||
|
case "${1:-start}" in
|
||||||
|
start)
|
||||||
|
show_banner
|
||||||
|
check_environment
|
||||||
|
create_directories
|
||||||
|
initialize_database
|
||||||
|
start_application
|
||||||
|
;;
|
||||||
|
health-check)
|
||||||
|
health_check
|
||||||
|
;;
|
||||||
|
init-db)
|
||||||
|
log_info "仅初始化数据库模式"
|
||||||
|
check_environment
|
||||||
|
create_directories
|
||||||
|
initialize_database
|
||||||
|
log_success "数据库初始化完成"
|
||||||
|
;;
|
||||||
|
shell)
|
||||||
|
log_info "进入交互式shell"
|
||||||
|
exec /bin/bash
|
||||||
|
;;
|
||||||
|
help|--help|-h)
|
||||||
|
show_help
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_error "未知选项: $1"
|
||||||
|
show_help
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# 执行主函数
|
||||||
|
main "$@"
|
@@ -1,51 +0,0 @@
|
|||||||
# DataTools Pro 设计文档
|
|
||||||
|
|
||||||
本目录包含DataTools Pro项目的完整设计文档,为开发者、运维人员和用户提供详细的技术参考。
|
|
||||||
|
|
||||||
## 文档结构
|
|
||||||
|
|
||||||
### 📋 核心设计文档
|
|
||||||
- **[系统架构设计](architecture.md)** - 整体系统架构、技术选型和设计原则
|
|
||||||
- **[API接口设计](api-design.md)** - REST API接口规范和数据结构定义
|
|
||||||
- **[数据库设计](database-design.md)** - 数据模型、表结构和关系设计
|
|
||||||
- **[前端模块化设计](frontend-architecture.md)** - 前端架构、模块化和组件设计
|
|
||||||
|
|
||||||
### 🚀 部署和运维
|
|
||||||
- **[部署指南](deployment.md)** - 生产环境部署、配置和监控
|
|
||||||
- **[运维手册](operations.md)** - 日常运维、故障排查和性能优化
|
|
||||||
|
|
||||||
### 📖 用户文档
|
|
||||||
- **[用户使用手册](user-manual.md)** - 功能使用指南和最佳实践
|
|
||||||
- **[开发者指南](developer-guide.md)** - 开发环境搭建和二次开发指南
|
|
||||||
|
|
||||||
### 🔧 技术规范
|
|
||||||
- **[代码规范](coding-standards.md)** - 代码风格、命名规范和最佳实践
|
|
||||||
- **[安全规范](security-guidelines.md)** - 安全设计、数据保护和风险管控
|
|
||||||
|
|
||||||
### 📊 业务文档
|
|
||||||
- **[需求分析](requirements.md)** - 业务需求、功能规格和用例分析
|
|
||||||
- **[测试计划](testing-plan.md)** - 测试策略、用例设计和质量保证
|
|
||||||
|
|
||||||
## 文档版本
|
|
||||||
|
|
||||||
| 文档版本 | 系统版本 | 更新日期 | 主要变更 |
|
|
||||||
|---------|---------|----------|----------|
|
|
||||||
| v2.0.0 | DataTools Pro 2.0 | 2024-08-05 | 完整的系统设计文档创建 |
|
|
||||||
|
|
||||||
## 文档贡献
|
|
||||||
|
|
||||||
如需更新文档,请遵循以下原则:
|
|
||||||
1. 保持文档结构清晰,使用标准Markdown格式
|
|
||||||
2. 及时更新版本信息和变更日志
|
|
||||||
3. 确保代码示例和配置信息的准确性
|
|
||||||
4. 添加必要的图片和流程图说明
|
|
||||||
|
|
||||||
## 联系方式
|
|
||||||
|
|
||||||
- **项目维护者**: DataTools Pro Team
|
|
||||||
- **技术支持**: 请提交Issue到项目仓库
|
|
||||||
- **文档反馈**: 欢迎提出改进建议
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**DataTools Pro** - 企业级数据处理与比对解决方案
|
|
@@ -1,759 +0,0 @@
|
|||||||
# DataTools Pro API接口设计文档
|
|
||||||
|
|
||||||
## 1. API概述
|
|
||||||
|
|
||||||
### 1.1 设计原则
|
|
||||||
- **RESTful设计**: 遵循REST架构风格
|
|
||||||
- **统一格式**: 标准化的请求和响应格式
|
|
||||||
- **版本控制**: 支持API版本管理
|
|
||||||
- **错误处理**: 完整的错误码和错误信息
|
|
||||||
- **安全性**: 输入验证和权限控制
|
|
||||||
|
|
||||||
### 1.2 基础信息
|
|
||||||
- **Base URL**: `http://localhost:5000`
|
|
||||||
- **Content-Type**: `application/json`
|
|
||||||
- **字符编码**: `UTF-8`
|
|
||||||
- **API版本**: `v1.0`
|
|
||||||
|
|
||||||
### 1.3 响应格式规范
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {},
|
|
||||||
"message": "操作成功",
|
|
||||||
"timestamp": "2024-08-05T10:30:00Z",
|
|
||||||
"request_id": "uuid-string"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2. 核心API端点
|
|
||||||
|
|
||||||
### 2.1 Cassandra数据比对API
|
|
||||||
|
|
||||||
#### 2.1.1 执行单表查询比对
|
|
||||||
**端点**: `POST /api/query`
|
|
||||||
|
|
||||||
**功能**: 执行Cassandra单表数据查询和比对分析
|
|
||||||
|
|
||||||
**请求参数**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"pro_config": {
|
|
||||||
"cluster_name": "production-cluster",
|
|
||||||
"datacenter": "datacenter1",
|
|
||||||
"hosts": ["10.0.1.100", "10.0.1.101"],
|
|
||||||
"port": 9042,
|
|
||||||
"username": "cassandra",
|
|
||||||
"password": "password",
|
|
||||||
"keyspace": "production_ks",
|
|
||||||
"table": "user_data"
|
|
||||||
},
|
|
||||||
"test_config": {
|
|
||||||
"cluster_name": "test-cluster",
|
|
||||||
"datacenter": "datacenter1",
|
|
||||||
"hosts": ["10.0.2.100"],
|
|
||||||
"port": 9042,
|
|
||||||
"username": "cassandra",
|
|
||||||
"password": "password",
|
|
||||||
"keyspace": "test_ks",
|
|
||||||
"table": "user_data"
|
|
||||||
},
|
|
||||||
"keys": ["user_id"],
|
|
||||||
"values": ["1001", "1002", "1003"],
|
|
||||||
"fields_to_compare": ["name", "email", "status"],
|
|
||||||
"exclude_fields": ["created_at", "updated_at"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应数据**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"total_keys": 3,
|
|
||||||
"pro_count": 3,
|
|
||||||
"test_count": 2,
|
|
||||||
"differences": [
|
|
||||||
{
|
|
||||||
"key": {"user_id": "1001"},
|
|
||||||
"field": "email",
|
|
||||||
"pro_value": "user1@prod.com",
|
|
||||||
"test_value": "user1@test.com",
|
|
||||||
"message": "字段值不匹配"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"identical_results": [
|
|
||||||
{
|
|
||||||
"key": {"user_id": "1002"},
|
|
||||||
"pro_fields": {"name": "User2", "email": "user2@example.com"},
|
|
||||||
"test_fields": {"name": "User2", "email": "user2@example.com"}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"field_diff_count": {
|
|
||||||
"email": 1
|
|
||||||
},
|
|
||||||
"raw_pro_data": [...],
|
|
||||||
"raw_test_data": [...],
|
|
||||||
"summary": {
|
|
||||||
"overview": "查询了3个Key,发现1处差异",
|
|
||||||
"percentages": {
|
|
||||||
"match_rate": 66.67,
|
|
||||||
"diff_rate": 33.33
|
|
||||||
},
|
|
||||||
"field_analysis": {
|
|
||||||
"email": {"diff_count": 1, "diff_rate": 33.33}
|
|
||||||
},
|
|
||||||
"recommendations": ["建议检查邮箱字段的数据同步"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"message": "查询比对完成",
|
|
||||||
"execution_time": 1.25,
|
|
||||||
"timestamp": "2024-08-05T10:30:00Z"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2.1.2 执行分表查询比对
|
|
||||||
**端点**: `POST /api/sharding-query`
|
|
||||||
|
|
||||||
**功能**: 执行Cassandra分表数据查询和比对分析
|
|
||||||
|
|
||||||
**请求参数**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"pro_config": { /* 同单表查询配置 */ },
|
|
||||||
"test_config": { /* 同单表查询配置 */ },
|
|
||||||
"keys": ["doc_id"],
|
|
||||||
"values": ["wmid_1609459200", "wmid_1609545600"],
|
|
||||||
"fields_to_compare": ["content", "status"],
|
|
||||||
"exclude_fields": [],
|
|
||||||
"sharding_config": {
|
|
||||||
"use_sharding_for_pro": true,
|
|
||||||
"use_sharding_for_test": false,
|
|
||||||
"interval_seconds": 604800,
|
|
||||||
"table_count": 14
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应数据**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
/* 基础比对结果同单表查询 */
|
|
||||||
"sharding_info": {
|
|
||||||
"pro_shard_mapping": {
|
|
||||||
"wmid_1609459200": "user_data_0",
|
|
||||||
"wmid_1609545600": "user_data_1"
|
|
||||||
},
|
|
||||||
"test_shard_mapping": {
|
|
||||||
"wmid_1609459200": "user_data",
|
|
||||||
"wmid_1609545600": "user_data"
|
|
||||||
},
|
|
||||||
"failed_keys": [],
|
|
||||||
"shard_stats": {
|
|
||||||
"pro_tables_used": ["user_data_0", "user_data_1"],
|
|
||||||
"test_tables_used": ["user_data"],
|
|
||||||
"timestamp_extraction_success_rate": 100.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"message": "分表查询比对完成",
|
|
||||||
"execution_time": 2.15
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.2 Redis集群比对API
|
|
||||||
|
|
||||||
#### 2.2.1 执行Redis集群比对
|
|
||||||
**端点**: `POST /api/redis/compare`
|
|
||||||
|
|
||||||
**功能**: 执行Redis集群数据比对分析
|
|
||||||
|
|
||||||
**请求参数**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"cluster1_config": {
|
|
||||||
"name": "生产集群",
|
|
||||||
"nodes": [
|
|
||||||
{"host": "10.0.1.100", "port": 6379},
|
|
||||||
{"host": "10.0.1.101", "port": 6380}
|
|
||||||
],
|
|
||||||
"password": "redis_password",
|
|
||||||
"socket_timeout": 3,
|
|
||||||
"socket_connect_timeout": 3,
|
|
||||||
"max_connections_per_node": 16
|
|
||||||
},
|
|
||||||
"cluster2_config": {
|
|
||||||
"name": "测试集群",
|
|
||||||
"nodes": [{"host": "10.0.2.100", "port": 6379}],
|
|
||||||
"password": null
|
|
||||||
},
|
|
||||||
"query_mode": "specified",
|
|
||||||
"keys": ["user:1001", "user:1002", "session:abc123"],
|
|
||||||
"sample_config": {
|
|
||||||
"count": 100,
|
|
||||||
"pattern": "*",
|
|
||||||
"source_cluster": "cluster2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应数据**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"total_keys": 3,
|
|
||||||
"cluster1_found": 2,
|
|
||||||
"cluster2_found": 3,
|
|
||||||
"differences": [
|
|
||||||
{
|
|
||||||
"key": "user:1001",
|
|
||||||
"cluster1_value": "{\"name\":\"John\",\"age\":25}",
|
|
||||||
"cluster2_value": "{\"name\":\"John\",\"age\":26}",
|
|
||||||
"value_type": "string",
|
|
||||||
"difference_type": "value_mismatch"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"identical": [
|
|
||||||
{
|
|
||||||
"key": "user:1002",
|
|
||||||
"value": "{\"name\":\"Jane\",\"age\":30}",
|
|
||||||
"value_type": "string"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"missing_in_cluster1": ["session:abc123"],
|
|
||||||
"missing_in_cluster2": [],
|
|
||||||
"cluster_stats": {
|
|
||||||
"cluster1": {
|
|
||||||
"connection_status": "connected",
|
|
||||||
"response_time_avg": 0.15,
|
|
||||||
"nodes_status": [
|
|
||||||
{"host": "10.0.1.100", "port": 6379, "status": "connected"},
|
|
||||||
{"host": "10.0.1.101", "port": 6380, "status": "connected"}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"cluster2": {
|
|
||||||
"connection_status": "connected",
|
|
||||||
"response_time_avg": 0.12,
|
|
||||||
"nodes_status": [
|
|
||||||
{"host": "10.0.2.100", "port": 6379, "status": "connected"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"performance_summary": {
|
|
||||||
"total_execution_time": 0.85,
|
|
||||||
"keys_per_second": 3.53,
|
|
||||||
"data_transferred_kb": 2.1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"message": "Redis集群比对完成"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.3 配置管理API
|
|
||||||
|
|
||||||
#### 2.3.1 获取默认配置
|
|
||||||
**端点**: `GET /api/default-config`
|
|
||||||
|
|
||||||
**功能**: 获取系统默认数据库配置
|
|
||||||
|
|
||||||
**响应数据**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"pro_config": {
|
|
||||||
"cluster_name": "production-cluster",
|
|
||||||
"datacenter": "datacenter1",
|
|
||||||
"hosts": ["127.0.0.1"],
|
|
||||||
"port": 9042,
|
|
||||||
"username": "",
|
|
||||||
"password": "",
|
|
||||||
"keyspace": "production_ks",
|
|
||||||
"table": "table_name"
|
|
||||||
},
|
|
||||||
"test_config": {
|
|
||||||
"cluster_name": "test-cluster",
|
|
||||||
"datacenter": "datacenter1",
|
|
||||||
"hosts": ["127.0.0.1"],
|
|
||||||
"port": 9042,
|
|
||||||
"username": "",
|
|
||||||
"password": "",
|
|
||||||
"keyspace": "test_ks",
|
|
||||||
"table": "table_name"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2.3.2 创建配置组
|
|
||||||
**端点**: `POST /api/config-groups`
|
|
||||||
|
|
||||||
**请求参数**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "生产环境配置",
|
|
||||||
"description": "生产环境Cassandra配置组",
|
|
||||||
"pro_config": { /* Cassandra配置 */ },
|
|
||||||
"test_config": { /* Cassandra配置 */ },
|
|
||||||
"query_config": {
|
|
||||||
"keys": ["user_id"],
|
|
||||||
"fields_to_compare": [],
|
|
||||||
"exclude_fields": []
|
|
||||||
},
|
|
||||||
"sharding_config": {
|
|
||||||
"use_sharding_for_pro": false,
|
|
||||||
"use_sharding_for_test": false,
|
|
||||||
"interval_seconds": 604800,
|
|
||||||
"table_count": 14
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应数据**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"id": 1,
|
|
||||||
"name": "生产环境配置",
|
|
||||||
"created_at": "2024-08-05T10:30:00Z"
|
|
||||||
},
|
|
||||||
"message": "配置组创建成功"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2.3.3 获取配置组列表
|
|
||||||
**端点**: `GET /api/config-groups`
|
|
||||||
|
|
||||||
**响应数据**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"name": "生产环境配置",
|
|
||||||
"description": "生产环境Cassandra配置组",
|
|
||||||
"created_at": "2024-08-05T10:30:00Z",
|
|
||||||
"updated_at": "2024-08-05T10:30:00Z"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2.3.4 获取特定配置组
|
|
||||||
**端点**: `GET /api/config-groups/{id}`
|
|
||||||
|
|
||||||
**响应数据**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"id": 1,
|
|
||||||
"name": "生产环境配置",
|
|
||||||
"description": "生产环境Cassandra配置组",
|
|
||||||
"pro_config": { /* 完整配置 */ },
|
|
||||||
"test_config": { /* 完整配置 */ },
|
|
||||||
"query_config": { /* 查询配置 */ },
|
|
||||||
"sharding_config": { /* 分表配置 */ },
|
|
||||||
"created_at": "2024-08-05T10:30:00Z",
|
|
||||||
"updated_at": "2024-08-05T10:30:00Z"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2.3.5 删除配置组
|
|
||||||
**端点**: `DELETE /api/config-groups/{id}`
|
|
||||||
|
|
||||||
**响应数据**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": null,
|
|
||||||
"message": "配置组删除成功"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.4 查询历史管理API
|
|
||||||
|
|
||||||
#### 2.4.1 获取查询历史列表
|
|
||||||
**端点**: `GET /api/query-history`
|
|
||||||
|
|
||||||
**查询参数**:
|
|
||||||
- `limit`: 返回记录数量限制 (默认50)
|
|
||||||
- `offset`: 偏移量 (默认0)
|
|
||||||
- `query_type`: 查询类型 (`single`/`sharding`)
|
|
||||||
|
|
||||||
**响应数据**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"name": "用户数据比对-20240805",
|
|
||||||
"description": "生产环境用户数据比对",
|
|
||||||
"query_type": "single",
|
|
||||||
"total_keys": 100,
|
|
||||||
"differences_count": 5,
|
|
||||||
"identical_count": 95,
|
|
||||||
"execution_time": 2.5,
|
|
||||||
"created_at": "2024-08-05T10:30:00Z"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"total": 1,
|
|
||||||
"has_more": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2.4.2 保存查询历史
|
|
||||||
**端点**: `POST /api/query-history`
|
|
||||||
|
|
||||||
**请求参数**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "用户数据比对-20240805",
|
|
||||||
"description": "生产环境用户数据比对",
|
|
||||||
"pro_config": { /* 生产配置 */ },
|
|
||||||
"test_config": { /* 测试配置 */ },
|
|
||||||
"query_config": { /* 查询配置 */ },
|
|
||||||
"query_keys": ["1001", "1002", "1003"],
|
|
||||||
"results_summary": {
|
|
||||||
"total_keys": 3,
|
|
||||||
"differences_count": 1,
|
|
||||||
"identical_count": 2
|
|
||||||
},
|
|
||||||
"execution_time": 1.25,
|
|
||||||
"query_type": "single",
|
|
||||||
"sharding_config": null,
|
|
||||||
"raw_results": { /* 完整查询结果 */ }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2.4.3 获取历史记录详情
|
|
||||||
**端点**: `GET /api/query-history/{id}`
|
|
||||||
|
|
||||||
**响应数据**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"id": 1,
|
|
||||||
"name": "用户数据比对-20240805",
|
|
||||||
"description": "生产环境用户数据比对",
|
|
||||||
"pro_config": { /* 完整配置 */ },
|
|
||||||
"test_config": { /* 完整配置 */ },
|
|
||||||
"query_config": { /* 查询配置 */ },
|
|
||||||
"query_keys": ["1001", "1002", "1003"],
|
|
||||||
"results_summary": { /* 结果摘要 */ },
|
|
||||||
"execution_time": 1.25,
|
|
||||||
"query_type": "single",
|
|
||||||
"created_at": "2024-08-05T10:30:00Z"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2.4.4 获取历史记录完整结果
|
|
||||||
**端点**: `GET /api/query-history/{id}/results`
|
|
||||||
|
|
||||||
**响应数据**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"differences": [ /* 完整差异数据 */ ],
|
|
||||||
"identical_results": [ /* 完整相同数据 */ ],
|
|
||||||
"raw_pro_data": [ /* 生产原始数据 */ ],
|
|
||||||
"raw_test_data": [ /* 测试原始数据 */ ],
|
|
||||||
"field_diff_count": { /* 字段差异统计 */ },
|
|
||||||
"summary": { /* 详细分析报告 */ }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.5 日志管理API
|
|
||||||
|
|
||||||
#### 2.5.1 获取查询日志
|
|
||||||
**端点**: `GET /api/query-logs`
|
|
||||||
|
|
||||||
**查询参数**:
|
|
||||||
- `limit`: 返回记录数量 (默认100)
|
|
||||||
- `level`: 日志级别 (`INFO`/`WARNING`/`ERROR`)
|
|
||||||
- `history_id`: 关联的历史记录ID
|
|
||||||
|
|
||||||
**响应数据**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"logs": [
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"batch_id": "batch-uuid-123",
|
|
||||||
"history_id": 1,
|
|
||||||
"timestamp": "2024-08-05T10:30:01.123Z",
|
|
||||||
"level": "INFO",
|
|
||||||
"message": "开始执行Cassandra查询",
|
|
||||||
"query_type": "cassandra_single",
|
|
||||||
"created_at": "2024-08-05T10:30:01Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"batch_id": "batch-uuid-123",
|
|
||||||
"history_id": 1,
|
|
||||||
"timestamp": "2024-08-05T10:30:02.456Z",
|
|
||||||
"level": "INFO",
|
|
||||||
"message": "生产环境查询完成,返回3条记录",
|
|
||||||
"query_type": "cassandra_single",
|
|
||||||
"created_at": "2024-08-05T10:30:02Z"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"total": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2.5.2 获取特定历史记录的日志
|
|
||||||
**端点**: `GET /api/query-logs/history/{id}`
|
|
||||||
|
|
||||||
**响应数据**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"history_id": 1,
|
|
||||||
"logs": [ /* 该历史记录相关的所有日志 */ ],
|
|
||||||
"log_summary": {
|
|
||||||
"total_logs": 10,
|
|
||||||
"info_count": 8,
|
|
||||||
"warning_count": 1,
|
|
||||||
"error_count": 1,
|
|
||||||
"start_time": "2024-08-05T10:30:00Z",
|
|
||||||
"end_time": "2024-08-05T10:30:05Z"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2.5.3 清空查询日志
|
|
||||||
**端点**: `DELETE /api/query-logs`
|
|
||||||
|
|
||||||
**响应数据**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"deleted_count": 150
|
|
||||||
},
|
|
||||||
"message": "查询日志清空成功"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.6 系统管理API
|
|
||||||
|
|
||||||
#### 2.6.1 初始化数据库
|
|
||||||
**端点**: `POST /api/init-db`
|
|
||||||
|
|
||||||
**功能**: 初始化SQLite数据库表结构
|
|
||||||
|
|
||||||
**响应数据**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"tables_created": [
|
|
||||||
"config_groups",
|
|
||||||
"query_history",
|
|
||||||
"query_logs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"message": "数据库初始化成功"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2.6.2 系统健康检查
|
|
||||||
**端点**: `GET /api/health`
|
|
||||||
|
|
||||||
**响应数据**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"status": "healthy",
|
|
||||||
"version": "2.0.0",
|
|
||||||
"uptime": "2 days, 3 hours, 45 minutes",
|
|
||||||
"database": {
|
|
||||||
"sqlite": {
|
|
||||||
"status": "connected",
|
|
||||||
"file_size_mb": 15.2
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"memory_usage": {
|
|
||||||
"used_mb": 128.5,
|
|
||||||
"available_mb": 3967.5
|
|
||||||
},
|
|
||||||
"last_check": "2024-08-05T10:30:00Z"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3. 错误处理
|
|
||||||
|
|
||||||
### 3.1 错误响应格式
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": false,
|
|
||||||
"error": {
|
|
||||||
"code": "VALIDATION_ERROR",
|
|
||||||
"message": "请求参数验证失败",
|
|
||||||
"details": {
|
|
||||||
"field": "pro_config.hosts",
|
|
||||||
"issue": "hosts字段不能为空"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"timestamp": "2024-08-05T10:30:00Z",
|
|
||||||
"request_id": "uuid-string"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 错误码定义
|
|
||||||
|
|
||||||
| 错误码 | HTTP状态码 | 说明 |
|
|
||||||
|--------|-----------|------|
|
|
||||||
| `VALIDATION_ERROR` | 400 | 请求参数验证失败 |
|
|
||||||
| `CONNECTION_ERROR` | 500 | 数据库连接失败 |
|
|
||||||
| `QUERY_ERROR` | 500 | 查询执行失败 |
|
|
||||||
| `TIMEOUT_ERROR` | 408 | 请求超时 |
|
|
||||||
| `NOT_FOUND` | 404 | 资源不存在 |
|
|
||||||
| `CONFLICT` | 409 | 资源冲突 |
|
|
||||||
| `SYSTEM_ERROR` | 500 | 系统内部错误 |
|
|
||||||
| `AUTH_ERROR` | 401 | 认证失败 |
|
|
||||||
| `PERMISSION_DENIED` | 403 | 权限不足 |
|
|
||||||
|
|
||||||
### 3.3 详细错误场景
|
|
||||||
|
|
||||||
#### 3.3.1 连接错误
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": false,
|
|
||||||
"error": {
|
|
||||||
"code": "CONNECTION_ERROR",
|
|
||||||
"message": "无法连接到Cassandra集群",
|
|
||||||
"details": {
|
|
||||||
"cluster": "production-cluster",
|
|
||||||
"hosts": ["10.0.1.100", "10.0.1.101"],
|
|
||||||
"error_detail": "Connection refused",
|
|
||||||
"suggestions": [
|
|
||||||
"检查网络连通性",
|
|
||||||
"验证主机地址和端口",
|
|
||||||
"确认Cassandra服务状态"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.3.2 查询错误
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": false,
|
|
||||||
"error": {
|
|
||||||
"code": "QUERY_ERROR",
|
|
||||||
"message": "CQL查询执行失败",
|
|
||||||
"details": {
|
|
||||||
"query": "SELECT * FROM user_data WHERE user_id IN (?)",
|
|
||||||
"error_detail": "Invalid keyspace name 'invalid_ks'",
|
|
||||||
"suggestions": [
|
|
||||||
"检查keyspace名称是否正确",
|
|
||||||
"确认表名拼写无误",
|
|
||||||
"验证字段名是否存在"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. 认证和授权
|
|
||||||
|
|
||||||
### 4.1 认证机制
|
|
||||||
当前版本暂未实现认证机制,所有API端点均为开放访问。在生产环境中建议实现以下认证方式:
|
|
||||||
|
|
||||||
- **API Key认证**: 基于API密钥的简单认证
|
|
||||||
- **JWT Token**: JSON Web Token认证
|
|
||||||
- **OAuth 2.0**: 标准OAuth认证流程
|
|
||||||
- **LDAP集成**: 企业级LDAP认证
|
|
||||||
|
|
||||||
### 4.2 权限控制
|
|
||||||
建议实施基于角色的访问控制(RBAC):
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"roles": [
|
|
||||||
{
|
|
||||||
"name": "admin",
|
|
||||||
"permissions": ["read", "write", "delete", "config"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "operator",
|
|
||||||
"permissions": ["read", "write"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "viewer",
|
|
||||||
"permissions": ["read"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5. API版本管理
|
|
||||||
|
|
||||||
### 5.1 版本策略
|
|
||||||
- **URL版本控制**: `/api/v1/query`, `/api/v2/query`
|
|
||||||
- **Header版本控制**: `Accept: application/vnd.datatools.v1+json`
|
|
||||||
- **向后兼容**: 保持旧版本API的兼容性
|
|
||||||
- **弃用策略**: 提前通知API弃用计划
|
|
||||||
|
|
||||||
### 5.2 版本变更记录
|
|
||||||
|
|
||||||
| API版本 | 发布日期 | 主要变更 | 兼容性 |
|
|
||||||
|---------|----------|----------|--------|
|
|
||||||
| v1.0 | 2024-08-05 | 初始版本发布 | N/A |
|
|
||||||
|
|
||||||
## 6. 性能和限制
|
|
||||||
|
|
||||||
### 6.1 API限制
|
|
||||||
- **请求频率**: 每分钟最多100次请求
|
|
||||||
- **并发连接**: 最多10个并发连接
|
|
||||||
- **响应大小**: 单次响应最大50MB
|
|
||||||
- **查询超时**: 默认120秒超时
|
|
||||||
|
|
||||||
### 6.2 性能优化
|
|
||||||
- **连接池**: 复用数据库连接
|
|
||||||
- **缓存策略**: 配置数据缓存
|
|
||||||
- **异步处理**: 长时间查询异步执行
|
|
||||||
- **分页处理**: 大数据集分页返回
|
|
||||||
|
|
||||||
## 7. 监控和日志
|
|
||||||
|
|
||||||
### 7.1 API监控指标
|
|
||||||
- **响应时间**: 平均响应时间和95分位数
|
|
||||||
- **成功率**: API调用成功率统计
|
|
||||||
- **错误率**: 各类错误的发生率
|
|
||||||
- **吞吐量**: 每秒处理的请求数
|
|
||||||
|
|
||||||
### 7.2 日志记录
|
|
||||||
- **访问日志**: 记录所有API访问
|
|
||||||
- **错误日志**: 详细的错误信息和堆栈
|
|
||||||
- **性能日志**: 慢查询和性能瓶颈
|
|
||||||
- **审计日志**: 重要操作的审计记录
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**版本**: v1.0
|
|
||||||
**更新日期**: 2024-08-05
|
|
||||||
**维护者**: DataTools Pro Team
|
|
@@ -1,387 +0,0 @@
|
|||||||
# DataTools Pro 系统架构设计
|
|
||||||
|
|
||||||
## 1. 系统概述
|
|
||||||
|
|
||||||
### 1.1 项目简介
|
|
||||||
DataTools Pro 是一个企业级数据处理与比对工具平台,专注于提供高效、精准、可视化的数据分析解决方案。系统支持Cassandra数据库和Redis集群的数据比对分析,具备分表查询、多主键查询、配置管理等企业级功能。
|
|
||||||
|
|
||||||
### 1.2 设计目标
|
|
||||||
- **高性能**: 支持大规模数据查询和比对处理
|
|
||||||
- **高可用**: 企业级稳定性和容错能力
|
|
||||||
- **易扩展**: 模块化设计,便于功能扩展
|
|
||||||
- **用户友好**: 直观的Web界面和操作体验
|
|
||||||
- **安全性**: 数据安全和访问控制
|
|
||||||
|
|
||||||
### 1.3 技术栈
|
|
||||||
```
|
|
||||||
前端技术栈:
|
|
||||||
├── 原生JavaScript (ES6+)
|
|
||||||
├── Bootstrap 5.1.3
|
|
||||||
├── Font Awesome 6.0.0
|
|
||||||
└── 模块化架构 (ES6 Modules)
|
|
||||||
|
|
||||||
后端技术栈:
|
|
||||||
├── Python 3.7+
|
|
||||||
├── Flask 2.3.3
|
|
||||||
├── Cassandra Driver 3.29.1
|
|
||||||
├── Redis Client
|
|
||||||
└── SQLite (配置存储)
|
|
||||||
|
|
||||||
数据存储:
|
|
||||||
├── Apache Cassandra (主要数据源)
|
|
||||||
├── Redis Cluster (缓存和数据源)
|
|
||||||
└── SQLite (配置和历史数据)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2. 整体架构
|
|
||||||
|
|
||||||
### 2.1 架构概览
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────────┐
|
|
||||||
│ DataTools Pro 架构图 │
|
|
||||||
├─────────────────────────────────────────────────────────────┤
|
|
||||||
│ 前端层 (Frontend Layer) │
|
|
||||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐│
|
|
||||||
│ │ 首页模块 │ │ Cassandra工具 │ │ Redis工具 ││
|
|
||||||
│ │ index.html │ │ db_compare.html │ │redis_compare.html││
|
|
||||||
│ └─────────────────┘ └─────────────────┘ └─────────────────┘│
|
|
||||||
│ │ │ │ │
|
|
||||||
│ ┌───────────────────────────────────────────────────────────┐│
|
|
||||||
│ │ 模块化JavaScript架构 ││
|
|
||||||
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐││
|
|
||||||
│ │ │ config │ │ utils │ │ api │ │ ui │ │ nav │││
|
|
||||||
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘││
|
|
||||||
│ └───────────────────────────────────────────────────────────┘│
|
|
||||||
├─────────────────────────────────────────────────────────────┤
|
|
||||||
│ 网关层 (Gateway Layer) │
|
|
||||||
│ ┌───────────────────────────────────────────────────────────┐│
|
|
||||||
│ │ Flask Web Server ││
|
|
||||||
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐││
|
|
||||||
│ │ │ 静态资源 │ │ API路由 │ │ 模板渲染 │││
|
|
||||||
│ │ │ 服务 │ │ 处理 │ │ 引擎 │││
|
|
||||||
│ │ └─────────────┘ └─────────────┘ └─────────────────────────┘││
|
|
||||||
│ └───────────────────────────────────────────────────────────┘│
|
|
||||||
├─────────────────────────────────────────────────────────────┤
|
|
||||||
│ 业务逻辑层 (Business Logic Layer) │
|
|
||||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐│
|
|
||||||
│ │ 查询引擎 │ │ 数据比对 │ │ 配置管理 ││
|
|
||||||
│ │ QueryEngine │ │ DataComparison │ │ ConfigManager ││
|
|
||||||
│ └─────────────────┘ └─────────────────┘ └─────────────────┘│
|
|
||||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐│
|
|
||||||
│ │ 分表处理 │ │ 日志收集 │ │ Redis处理 ││
|
|
||||||
│ │ ShardingCalc │ │ QueryLogger │ │ RedisClient ││
|
|
||||||
│ └─────────────────┘ └─────────────────┘ └─────────────────┘│
|
|
||||||
├─────────────────────────────────────────────────────────────┤
|
|
||||||
│ 数据访问层 (Data Access Layer) │
|
|
||||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐│
|
|
||||||
│ │ Cassandra │ │ Redis Cluster │ │ SQLite DB ││
|
|
||||||
│ │ 客户端 │ │ 客户端 │ │ 本地存储 ││
|
|
||||||
│ └─────────────────┘ └─────────────────┘ └─────────────────┘│
|
|
||||||
├─────────────────────────────────────────────────────────────┤
|
|
||||||
│ 数据存储层 (Data Storage Layer) │
|
|
||||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐│
|
|
||||||
│ │ 生产环境 │ │ 测试环境 │ │ 配置数据 ││
|
|
||||||
│ │ Cassandra │ │ Cassandra │ │ SQLite ││
|
|
||||||
│ └─────────────────┘ └─────────────────┘ └─────────────────┘│
|
|
||||||
│ ┌─────────────────┐ ┌─────────────────┐ │
|
|
||||||
│ │ 生产Redis │ │ 测试Redis │ │
|
|
||||||
│ │ 集群 │ │ 集群 │ │
|
|
||||||
│ └─────────────────┘ └─────────────────┘ │
|
|
||||||
└─────────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.2 核心组件
|
|
||||||
|
|
||||||
#### 2.2.1 前端架构
|
|
||||||
```javascript
|
|
||||||
// 模块化架构设计
|
|
||||||
static/js/
|
|
||||||
├── app-main.js // 主应用入口,统一管理
|
|
||||||
└── modules/
|
|
||||||
├── config.js // 配置管理 - API端点、UI配置
|
|
||||||
├── utils.js // 工具函数 - 通用功能库
|
|
||||||
├── api.js // HTTP客户端 - 统一请求处理
|
|
||||||
├── ui.js // UI组件 - 提示、模态框、分页
|
|
||||||
└── navigation.js // 导航管理 - 路由、面包屑、快捷键
|
|
||||||
```
|
|
||||||
|
|
||||||
**设计模式**: 模块化 + 单例模式
|
|
||||||
**核心特性**:
|
|
||||||
- ES6模块化,避免全局变量污染
|
|
||||||
- 统一的配置管理和错误处理
|
|
||||||
- 响应式UI组件和交互增强
|
|
||||||
- 键盘快捷键和无障碍支持
|
|
||||||
|
|
||||||
#### 2.2.2 后端架构
|
|
||||||
```python
|
|
||||||
# 模块化后端设计
|
|
||||||
modules/
|
|
||||||
├── api_routes.py // API路由定义和请求处理
|
|
||||||
├── cassandra_client.py // Cassandra数据库连接和操作
|
|
||||||
├── redis_client.py // Redis集群连接和操作
|
|
||||||
├── query_engine.py // 查询引擎和SQL构建
|
|
||||||
├── data_comparison.py // 数据比对算法和分析
|
|
||||||
├── sharding.py // 分表计算和时间戳处理
|
|
||||||
├── config_manager.py // 配置组管理和持久化
|
|
||||||
├── query_logger.py // 查询日志收集和管理
|
|
||||||
└── database.py // SQLite数据库操作
|
|
||||||
```
|
|
||||||
|
|
||||||
**设计模式**: 分层架构 + 依赖注入
|
|
||||||
**核心特性**:
|
|
||||||
- 单一职责原则,模块解耦
|
|
||||||
- 统一的错误处理和日志记录
|
|
||||||
- 连接池管理和资源优化
|
|
||||||
- 配置驱动的灵活部署
|
|
||||||
|
|
||||||
## 3. 核心功能模块
|
|
||||||
|
|
||||||
### 3.1 Cassandra数据比对模块
|
|
||||||
|
|
||||||
#### 3.1.1 功能特性
|
|
||||||
- **多模式查询**: 单表查询、分表查询、多主键查询
|
|
||||||
- **时间分表支持**: TWCS (Time Window Compaction Strategy) 分表计算
|
|
||||||
- **复合主键**: 支持多字段组合主键查询
|
|
||||||
- **字段级比对**: 支持包含/排除字段的精细化比较
|
|
||||||
- **JSON处理**: 智能JSON字段识别和深度比较
|
|
||||||
|
|
||||||
#### 3.1.2 核心算法
|
|
||||||
```python
|
|
||||||
# 分表索引计算算法
|
|
||||||
def calculate_shard_index(timestamp, interval_seconds, table_count):
|
|
||||||
"""
|
|
||||||
计算分表索引
|
|
||||||
- timestamp: 从Key中提取的时间戳
|
|
||||||
- interval_seconds: 分表时间间隔(默认604800秒=7天)
|
|
||||||
- table_count: 分表数量(默认14张表)
|
|
||||||
"""
|
|
||||||
return int(timestamp) // interval_seconds % table_count
|
|
||||||
|
|
||||||
# 时间戳提取算法
|
|
||||||
def extract_timestamp_from_key(key):
|
|
||||||
"""
|
|
||||||
从Key中提取时间戳 - 删除所有非数字字符
|
|
||||||
示例: 'wmid_1609459200' -> 1609459200
|
|
||||||
"""
|
|
||||||
return re.sub(r'\D', '', key)
|
|
||||||
|
|
||||||
# 复合主键匹配算法
|
|
||||||
def match_composite_key(record, key_fields, key_values):
|
|
||||||
"""
|
|
||||||
复合主键匹配逻辑
|
|
||||||
- 支持单主键和复合主键统一处理
|
|
||||||
- 自动类型转换和字段验证
|
|
||||||
"""
|
|
||||||
if len(key_fields) == 1:
|
|
||||||
return str(record.get(key_fields[0])) == str(key_values[0])
|
|
||||||
else:
|
|
||||||
return all(
|
|
||||||
str(record.get(field)) == str(value)
|
|
||||||
for field, value in zip(key_fields, key_values)
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 Redis集群比对模块
|
|
||||||
|
|
||||||
#### 3.2.1 功能特性
|
|
||||||
- **多数据类型支持**: String、Hash、List、Set、ZSet
|
|
||||||
- **查询模式**: 随机采样、指定Key查询
|
|
||||||
- **集群管理**: 多节点集群配置和连接管理
|
|
||||||
- **性能监控**: 连接状态、查询耗时统计
|
|
||||||
- **批量操作**: 大批量Key的并行处理
|
|
||||||
|
|
||||||
#### 3.2.2 Redis集群架构
|
|
||||||
```python
|
|
||||||
# Redis集群连接配置
|
|
||||||
{
|
|
||||||
"cluster_name": "生产集群",
|
|
||||||
"nodes": [
|
|
||||||
{"host": "127.0.0.1", "port": 6379},
|
|
||||||
{"host": "127.0.0.1", "port": 6380}
|
|
||||||
],
|
|
||||||
"password": "optional_password",
|
|
||||||
"socket_timeout": 3,
|
|
||||||
"socket_connect_timeout": 3,
|
|
||||||
"max_connections_per_node": 16
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.3 数据比对引擎
|
|
||||||
|
|
||||||
#### 3.3.1 比对算法
|
|
||||||
```python
|
|
||||||
# 数据比对核心逻辑
|
|
||||||
class DataComparison:
|
|
||||||
def compare_results(self, pro_data, test_data, key_fields):
|
|
||||||
"""
|
|
||||||
数据比对主算法:
|
|
||||||
1. 构建数据索引映射 (基于主键)
|
|
||||||
2. 逐字段深度比较
|
|
||||||
3. JSON字段特殊处理
|
|
||||||
4. 数组字段顺序无关比较
|
|
||||||
5. 生成差异报告和统计
|
|
||||||
"""
|
|
||||||
differences = []
|
|
||||||
identical_results = []
|
|
||||||
field_diff_count = {}
|
|
||||||
|
|
||||||
# 构建生产数据索引
|
|
||||||
pro_index = self.build_data_index(pro_data, key_fields)
|
|
||||||
test_index = self.build_data_index(test_data, key_fields)
|
|
||||||
|
|
||||||
# 执行比对逻辑
|
|
||||||
for key, pro_record in pro_index.items():
|
|
||||||
if key in test_index:
|
|
||||||
test_record = test_index[key]
|
|
||||||
diffs = self.compare_records(pro_record, test_record)
|
|
||||||
if diffs:
|
|
||||||
differences.extend(diffs)
|
|
||||||
else:
|
|
||||||
identical_results.append({
|
|
||||||
'key': key,
|
|
||||||
'pro_fields': pro_record,
|
|
||||||
'test_fields': test_record
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
'differences': differences,
|
|
||||||
'identical_results': identical_results,
|
|
||||||
'field_diff_count': field_diff_count,
|
|
||||||
'summary': self.generate_summary(differences, identical_results)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.3.2 JSON处理算法
|
|
||||||
```python
|
|
||||||
def compare_json_values(pro_value, test_value):
|
|
||||||
"""
|
|
||||||
JSON值比较算法:
|
|
||||||
1. 智能检测JSON格式
|
|
||||||
2. 规范化JSON字符串
|
|
||||||
3. 深度比较对象结构
|
|
||||||
4. 数组顺序无关比较
|
|
||||||
"""
|
|
||||||
if self.is_json_field(pro_value) and self.is_json_field(test_value):
|
|
||||||
try:
|
|
||||||
pro_obj = json.loads(pro_value)
|
|
||||||
test_obj = json.loads(test_value)
|
|
||||||
return self.deep_compare_objects(pro_obj, test_obj)
|
|
||||||
except:
|
|
||||||
return pro_value == test_value
|
|
||||||
return pro_value == test_value
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. 数据流架构
|
|
||||||
|
|
||||||
### 4.1 查询执行流程
|
|
||||||
```mermaid
|
|
||||||
graph TD
|
|
||||||
A[用户请求] --> B[前端验证]
|
|
||||||
B --> C[API路由]
|
|
||||||
C --> D{查询类型}
|
|
||||||
D -->|单表查询| E[QueryEngine.execute_query]
|
|
||||||
D -->|分表查询| F[QueryEngine.execute_sharding_query]
|
|
||||||
E --> G[数据库连接]
|
|
||||||
F --> H[分表计算]
|
|
||||||
H --> G
|
|
||||||
G --> I[并行查询执行]
|
|
||||||
I --> J[数据收集]
|
|
||||||
J --> K[DataComparison.compare_results]
|
|
||||||
K --> L[结果分析]
|
|
||||||
L --> M[日志记录]
|
|
||||||
M --> N[响应返回]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.2 配置管理流程
|
|
||||||
```mermaid
|
|
||||||
graph TD
|
|
||||||
A[配置创建/更新] --> B[ConfigManager验证]
|
|
||||||
B --> C[SQLite持久化]
|
|
||||||
C --> D[配置缓存更新]
|
|
||||||
D --> E[前端状态同步]
|
|
||||||
|
|
||||||
F[配置加载] --> G[SQLite查询]
|
|
||||||
G --> H[配置反序列化]
|
|
||||||
H --> I[前端配置应用]
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5. 性能优化策略
|
|
||||||
|
|
||||||
### 5.1 查询优化
|
|
||||||
- **连接池管理**: 复用数据库连接,减少连接开销
|
|
||||||
- **并行查询**: 多线程并行执行生产和测试环境查询
|
|
||||||
- **分批处理**: 大量Key分批查询,避免内存溢出
|
|
||||||
- **索引优化**: 合理设计Cassandra分区键和聚簇键
|
|
||||||
|
|
||||||
### 5.2 前端性能
|
|
||||||
- **懒加载**: 按需加载JS模块和数据
|
|
||||||
- **虚拟分页**: 大数据集的分页展示
|
|
||||||
- **防抖节流**: 用户输入和搜索的性能优化
|
|
||||||
- **缓存策略**: 查询结果和配置数据的客户端缓存
|
|
||||||
|
|
||||||
### 5.3 内存管理
|
|
||||||
- **流式处理**: 大数据集的流式读取和处理
|
|
||||||
- **及时释放**: 查询完成后及时释放数据库连接和内存
|
|
||||||
- **垃圾回收**: Python和JavaScript的内存回收优化
|
|
||||||
|
|
||||||
## 6. 扩展性设计
|
|
||||||
|
|
||||||
### 6.1 水平扩展
|
|
||||||
- **无状态设计**: 应用层无状态,支持负载均衡
|
|
||||||
- **配置外部化**: 支持配置中心和环境变量
|
|
||||||
- **容器化**: Docker容器化部署
|
|
||||||
- **微服务拆分**: 可拆分为独立的查询服务和比对服务
|
|
||||||
|
|
||||||
### 6.2 功能扩展
|
|
||||||
- **插件架构**: 支持自定义数据源和比对算法
|
|
||||||
- **API开放**: RESTful API支持第三方集成
|
|
||||||
- **规则引擎**: 可配置的数据验证和比对规则
|
|
||||||
- **报告生成**: 支持多种格式的报告导出
|
|
||||||
|
|
||||||
## 7. 安全架构
|
|
||||||
|
|
||||||
### 7.1 数据安全
|
|
||||||
- **连接加密**: 支持SSL/TLS加密连接
|
|
||||||
- **密码管理**: 敏感信息加密存储
|
|
||||||
- **访问控制**: 基于角色的权限管理
|
|
||||||
- **审计日志**: 完整的操作审计记录
|
|
||||||
|
|
||||||
### 7.2 系统安全
|
|
||||||
- **输入验证**: 严格的参数验证和SQL注入防护
|
|
||||||
- **错误处理**: 不暴露敏感系统信息
|
|
||||||
- **会话管理**: 安全的会话管理机制
|
|
||||||
- **网络安全**: 防火墙和网络隔离
|
|
||||||
|
|
||||||
## 8. 监控和运维
|
|
||||||
|
|
||||||
### 8.1 系统监控
|
|
||||||
- **性能指标**: 查询耗时、成功率、错误率
|
|
||||||
- **资源监控**: CPU、内存、网络使用情况
|
|
||||||
- **业务指标**: 查询量、用户活跃度、功能使用统计
|
|
||||||
- **告警机制**: 异常情况的实时告警
|
|
||||||
|
|
||||||
### 8.2 日志管理
|
|
||||||
- **结构化日志**: JSON格式的结构化日志
|
|
||||||
- **日志分级**: INFO、WARNING、ERROR、DEBUG
|
|
||||||
- **日志轮转**: 自动日志清理和归档
|
|
||||||
- **日志分析**: 支持ELK等日志分析工具
|
|
||||||
|
|
||||||
## 9. 技术债务和改进计划
|
|
||||||
|
|
||||||
### 9.1 当前技术债务
|
|
||||||
- **单体应用**: 所有功能集中在单个Flask应用中
|
|
||||||
- **前端技术**: 使用原生JavaScript,缺乏现代化框架
|
|
||||||
- **测试覆盖**: 缺乏完整的单元测试和集成测试
|
|
||||||
- **文档完善**: API文档和代码注释需要完善
|
|
||||||
|
|
||||||
### 9.2 改进计划
|
|
||||||
- **微服务改造**: 逐步拆分为独立的微服务
|
|
||||||
- **前端升级**: 考虑引入Vue.js或React框架
|
|
||||||
- **CI/CD**: 建立完整的持续集成和部署流程
|
|
||||||
- **性能测试**: 建立性能基准和压力测试
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**版本**: v2.0.0
|
|
||||||
**更新日期**: 2024-08-05
|
|
||||||
**维护者**: DataTools Pro Team
|
|
@@ -1,603 +0,0 @@
|
|||||||
# DataTools Pro 数据库设计文档
|
|
||||||
|
|
||||||
## 1. 数据库概述
|
|
||||||
|
|
||||||
### 1.1 数据库架构
|
|
||||||
DataTools Pro采用多数据库架构,针对不同的数据存储需求选择最适合的数据库技术:
|
|
||||||
|
|
||||||
```
|
|
||||||
数据库架构图:
|
|
||||||
┌─────────────────────────────────────────────────────────┐
|
|
||||||
│ DataTools Pro 数据库架构 │
|
|
||||||
├─────────────────────────────────────────────────────────┤
|
|
||||||
│ 应用层 (Application Layer) │
|
|
||||||
│ ┌─────────────────────────────────────────────────────┐ │
|
|
||||||
│ │ Flask应用程序 │ │
|
|
||||||
│ └─────────────────────────────────────────────────────┘ │
|
|
||||||
├─────────────────────────────────────────────────────────┤
|
|
||||||
│ 数据访问层 (Data Access Layer) │
|
|
||||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
|
|
||||||
│ │ Cassandra │ │ Redis │ │ SQLite │ │
|
|
||||||
│ │ Driver │ │ Client │ │ Driver │ │
|
|
||||||
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
|
|
||||||
├─────────────────────────────────────────────────────────┤
|
|
||||||
│ 数据存储层 (Data Storage Layer) │
|
|
||||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
|
|
||||||
│ │ Apache │ │ Redis │ │ SQLite │ │
|
|
||||||
│ │ Cassandra │ │ Cluster │ │ 本地文件 │ │
|
|
||||||
│ │ 集群 │ │ │ │ config_groups.db │ │
|
|
||||||
│ │ (外部数据源) │ │ (外部数据源) │ │ (配置和历史数据) │ │
|
|
||||||
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
|
|
||||||
└─────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.2 数据库分类
|
|
||||||
|
|
||||||
#### 1.2.1 外部数据源
|
|
||||||
- **Apache Cassandra**: 主要的数据比对源,存储业务数据
|
|
||||||
- **Redis Cluster**: 缓存和高性能数据存储,支持多种数据类型
|
|
||||||
|
|
||||||
#### 1.2.2 内部存储
|
|
||||||
- **SQLite**: 轻量级关系数据库,存储系统配置、查询历史和日志
|
|
||||||
|
|
||||||
### 1.3 设计原则
|
|
||||||
- **数据隔离**: 不同类型数据使用不同的存储方案
|
|
||||||
- **性能优化**: 根据访问模式选择合适的数据库
|
|
||||||
- **易维护**: 简化数据库管理和备份恢复
|
|
||||||
- **扩展性**: 支持数据量增长和功能扩展
|
|
||||||
|
|
||||||
## 2. SQLite数据库设计
|
|
||||||
|
|
||||||
### 2.1 数据库文件
|
|
||||||
- **文件名**: `config_groups.db`
|
|
||||||
- **位置**: 项目根目录
|
|
||||||
- **编码**: UTF-8
|
|
||||||
- **版本**: SQLite 3.x
|
|
||||||
|
|
||||||
### 2.2 表结构设计
|
|
||||||
|
|
||||||
#### 2.2.1 config_groups表 - 配置组管理
|
|
||||||
```sql
|
|
||||||
CREATE TABLE config_groups (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
name TEXT NOT NULL UNIQUE, -- 配置组名称
|
|
||||||
description TEXT, -- 描述信息
|
|
||||||
pro_config TEXT NOT NULL, -- 生产环境配置(JSON)
|
|
||||||
test_config TEXT NOT NULL, -- 测试环境配置(JSON)
|
|
||||||
query_config TEXT NOT NULL, -- 查询配置(JSON)
|
|
||||||
sharding_config TEXT, -- 分表配置(JSON)
|
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 索引
|
|
||||||
CREATE INDEX idx_config_groups_name ON config_groups(name);
|
|
||||||
CREATE INDEX idx_config_groups_created_at ON config_groups(created_at);
|
|
||||||
```
|
|
||||||
|
|
||||||
**字段详解**:
|
|
||||||
- `id`: 主键,自增整数
|
|
||||||
- `name`: 配置组名称,唯一索引
|
|
||||||
- `description`: 配置组描述,可为空
|
|
||||||
- `pro_config`: 生产环境Cassandra配置,JSON格式
|
|
||||||
- `test_config`: 测试环境Cassandra配置,JSON格式
|
|
||||||
- `query_config`: 查询参数配置,JSON格式
|
|
||||||
- `sharding_config`: 分表查询配置,JSON格式
|
|
||||||
- `created_at`: 创建时间
|
|
||||||
- `updated_at`: 更新时间
|
|
||||||
|
|
||||||
**JSON配置示例**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"pro_config": {
|
|
||||||
"cluster_name": "production-cluster",
|
|
||||||
"datacenter": "datacenter1",
|
|
||||||
"hosts": ["10.0.1.100", "10.0.1.101"],
|
|
||||||
"port": 9042,
|
|
||||||
"username": "cassandra",
|
|
||||||
"password": "encrypted_password",
|
|
||||||
"keyspace": "production_ks",
|
|
||||||
"table": "user_data"
|
|
||||||
},
|
|
||||||
"query_config": {
|
|
||||||
"keys": ["user_id"],
|
|
||||||
"fields_to_compare": ["name", "email", "status"],
|
|
||||||
"exclude_fields": ["created_at", "updated_at"]
|
|
||||||
},
|
|
||||||
"sharding_config": {
|
|
||||||
"use_sharding_for_pro": true,
|
|
||||||
"use_sharding_for_test": false,
|
|
||||||
"interval_seconds": 604800,
|
|
||||||
"table_count": 14
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2.2.2 query_history表 - 查询历史记录
|
|
||||||
```sql
|
|
||||||
CREATE TABLE query_history (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
name TEXT NOT NULL, -- 历史记录名称
|
|
||||||
description TEXT, -- 描述信息
|
|
||||||
pro_config TEXT NOT NULL, -- 生产环境配置(JSON)
|
|
||||||
test_config TEXT NOT NULL, -- 测试环境配置(JSON)
|
|
||||||
query_config TEXT NOT NULL, -- 查询配置(JSON)
|
|
||||||
query_keys TEXT NOT NULL, -- 查询的键值(JSON Array)
|
|
||||||
results_summary TEXT NOT NULL, -- 结果摘要(JSON)
|
|
||||||
execution_time REAL NOT NULL, -- 执行时间(秒)
|
|
||||||
total_keys INTEGER NOT NULL, -- 总Key数量
|
|
||||||
differences_count INTEGER NOT NULL, -- 差异数量
|
|
||||||
identical_count INTEGER NOT NULL, -- 相同数量
|
|
||||||
query_type TEXT NOT NULL DEFAULT 'single', -- 查询类型(single/sharding)
|
|
||||||
sharding_config TEXT, -- 分表配置(JSON)
|
|
||||||
raw_results TEXT, -- 完整查询结果(JSON)
|
|
||||||
differences_data TEXT, -- 差异详细数据(JSON)
|
|
||||||
identical_data TEXT, -- 相同数据详情(JSON)
|
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 索引
|
|
||||||
CREATE INDEX idx_query_history_name ON query_history(name);
|
|
||||||
CREATE INDEX idx_query_history_query_type ON query_history(query_type);
|
|
||||||
CREATE INDEX idx_query_history_created_at ON query_history(created_at);
|
|
||||||
CREATE INDEX idx_query_history_execution_time ON query_history(execution_time);
|
|
||||||
```
|
|
||||||
|
|
||||||
**字段详解**:
|
|
||||||
- `id`: 主键,自增整数
|
|
||||||
- `name`: 历史记录名称
|
|
||||||
- `description`: 历史记录描述
|
|
||||||
- `pro_config`: 生产环境配置快照
|
|
||||||
- `test_config`: 测试环境配置快照
|
|
||||||
- `query_config`: 查询配置快照
|
|
||||||
- `query_keys`: 查询的Key值列表
|
|
||||||
- `results_summary`: 查询结果摘要统计
|
|
||||||
- `execution_time`: 查询执行时间
|
|
||||||
- `total_keys`: 查询的总Key数量
|
|
||||||
- `differences_count`: 发现的差异数量
|
|
||||||
- `identical_count`: 相同记录数量
|
|
||||||
- `query_type`: 查询类型(single/sharding)
|
|
||||||
- `sharding_config`: 分表配置(仅分表查询)
|
|
||||||
- `raw_results`: 完整的查询结果数据
|
|
||||||
- `differences_data`: 差异数据详情
|
|
||||||
- `identical_data`: 相同数据详情
|
|
||||||
- `created_at`: 创建时间
|
|
||||||
|
|
||||||
#### 2.2.3 query_logs表 - 查询日志记录
|
|
||||||
```sql
|
|
||||||
CREATE TABLE query_logs (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
batch_id TEXT NOT NULL, -- 批次ID(UUID)
|
|
||||||
history_id INTEGER, -- 关联历史记录ID
|
|
||||||
timestamp DATETIME NOT NULL, -- 日志时间戳
|
|
||||||
level TEXT NOT NULL, -- 日志级别(INFO/WARNING/ERROR)
|
|
||||||
message TEXT NOT NULL, -- 日志消息
|
|
||||||
query_type TEXT NOT NULL, -- 查询类型标识
|
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
|
|
||||||
FOREIGN KEY (history_id) REFERENCES query_history(id) ON DELETE SET NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 索引
|
|
||||||
CREATE INDEX idx_query_logs_batch_id ON query_logs(batch_id);
|
|
||||||
CREATE INDEX idx_query_logs_history_id ON query_logs(history_id);
|
|
||||||
CREATE INDEX idx_query_logs_level ON query_logs(level);
|
|
||||||
CREATE INDEX idx_query_logs_timestamp ON query_logs(timestamp);
|
|
||||||
CREATE INDEX idx_query_logs_query_type ON query_logs(query_type);
|
|
||||||
```
|
|
||||||
|
|
||||||
**字段详解**:
|
|
||||||
- `id`: 主键,自增整数
|
|
||||||
- `batch_id`: 批次标识,同一次查询操作的日志共享同一个batch_id
|
|
||||||
- `history_id`: 关联的历史记录ID,外键关联
|
|
||||||
- `timestamp`: 日志产生的精确时间戳
|
|
||||||
- `level`: 日志级别(INFO/WARNING/ERROR)
|
|
||||||
- `message`: 日志消息内容
|
|
||||||
- `query_type`: 查询类型标识(cassandra_single/cassandra_sharding/redis_compare)
|
|
||||||
- `created_at`: 记录创建时间
|
|
||||||
|
|
||||||
### 2.3 数据库触发器
|
|
||||||
|
|
||||||
#### 2.3.1 自动更新updated_at触发器
|
|
||||||
```sql
|
|
||||||
-- 配置组更新时间触发器
|
|
||||||
CREATE TRIGGER update_config_groups_updated_at
|
|
||||||
AFTER UPDATE ON config_groups
|
|
||||||
FOR EACH ROW
|
|
||||||
BEGIN
|
|
||||||
UPDATE config_groups
|
|
||||||
SET updated_at = CURRENT_TIMESTAMP
|
|
||||||
WHERE id = NEW.id;
|
|
||||||
END;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.4 数据库视图
|
|
||||||
|
|
||||||
#### 2.4.1 查询历史统计视图
|
|
||||||
```sql
|
|
||||||
CREATE VIEW query_history_stats AS
|
|
||||||
SELECT
|
|
||||||
query_type,
|
|
||||||
COUNT(*) as total_queries,
|
|
||||||
AVG(execution_time) as avg_execution_time,
|
|
||||||
AVG(total_keys) as avg_keys_per_query,
|
|
||||||
AVG(differences_count) as avg_differences,
|
|
||||||
AVG(CAST(differences_count AS REAL) / total_keys * 100) as avg_diff_rate,
|
|
||||||
MIN(created_at) as first_query,
|
|
||||||
MAX(created_at) as last_query
|
|
||||||
FROM query_history
|
|
||||||
GROUP BY query_type;
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2.4.2 日志统计视图
|
|
||||||
```sql
|
|
||||||
CREATE VIEW query_logs_stats AS
|
|
||||||
SELECT
|
|
||||||
DATE(created_at) as log_date,
|
|
||||||
level,
|
|
||||||
query_type,
|
|
||||||
COUNT(*) as log_count
|
|
||||||
FROM query_logs
|
|
||||||
GROUP BY DATE(created_at), level, query_type
|
|
||||||
ORDER BY log_date DESC, level;
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3. Cassandra数据库设计
|
|
||||||
|
|
||||||
### 3.1 数据模型理解
|
|
||||||
DataTools Pro作为数据比对工具,**不直接管理**Cassandra的数据模型,而是连接到现有的Cassandra集群进行数据查询和比对。
|
|
||||||
|
|
||||||
### 3.2 支持的Cassandra特性
|
|
||||||
|
|
||||||
#### 3.2.1 表结构支持
|
|
||||||
- **单表查询**: 支持任意Cassandra表结构
|
|
||||||
- **分表查询**: 支持TWCS(Time Window Compaction Strategy)分表模式
|
|
||||||
- **复合主键**: 支持多字段组合主键
|
|
||||||
- **所有数据类型**: text、int、timestamp、uuid、blob等
|
|
||||||
|
|
||||||
#### 3.2.2 查询模式
|
|
||||||
```cql
|
|
||||||
-- 单主键查询示例
|
|
||||||
SELECT * FROM keyspace.table_name
|
|
||||||
WHERE primary_key IN (?, ?, ?);
|
|
||||||
|
|
||||||
-- 复合主键查询示例
|
|
||||||
SELECT * FROM keyspace.table_name
|
|
||||||
WHERE (key1='value1' AND key2='value2')
|
|
||||||
OR (key1='value3' AND key2='value4');
|
|
||||||
|
|
||||||
-- 分表查询示例
|
|
||||||
SELECT * FROM keyspace.table_name_0
|
|
||||||
WHERE primary_key IN (?, ?);
|
|
||||||
SELECT * FROM keyspace.table_name_1
|
|
||||||
WHERE primary_key IN (?);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.2.3 分表命名规范
|
|
||||||
```
|
|
||||||
基础表名: user_data
|
|
||||||
分表命名: user_data_0, user_data_1, user_data_2, ..., user_data_13
|
|
||||||
分表计算: shard_index = timestamp // interval_seconds % table_count
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.3 连接管理
|
|
||||||
```python
|
|
||||||
# Cassandra连接配置
|
|
||||||
cassandra_config = {
|
|
||||||
"cluster_name": "production-cluster",
|
|
||||||
"datacenter": "datacenter1",
|
|
||||||
"hosts": ["10.0.1.100", "10.0.1.101", "10.0.1.102"],
|
|
||||||
"port": 9042,
|
|
||||||
"username": "app_user",
|
|
||||||
"password": "secure_password",
|
|
||||||
"keyspace": "application_data",
|
|
||||||
"table": "user_profiles"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 连接池配置
|
|
||||||
connection_settings = {
|
|
||||||
"connect_timeout": 10,
|
|
||||||
"request_timeout": 30,
|
|
||||||
"load_balancing_policy": "DCAwareRoundRobinPolicy",
|
|
||||||
"retry_policy": "RetryPolicy",
|
|
||||||
"compression": "lz4"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. Redis数据库设计
|
|
||||||
|
|
||||||
### 4.1 数据模型理解
|
|
||||||
DataTools Pro支持Redis集群的数据比对,**不直接管理**Redis的数据结构,而是连接到现有的Redis集群进行数据查询和比对。
|
|
||||||
|
|
||||||
### 4.2 支持的Redis数据类型
|
|
||||||
|
|
||||||
#### 4.2.1 基本数据类型
|
|
||||||
- **String**: 字符串类型,最常用的数据类型
|
|
||||||
- **Hash**: 哈希表,存储字段-值对
|
|
||||||
- **List**: 列表,有序的字符串集合
|
|
||||||
- **Set**: 集合,无序的字符串集合
|
|
||||||
- **Sorted Set**: 有序集合,带分数的有序字符串集合
|
|
||||||
|
|
||||||
#### 4.2.2 查询操作
|
|
||||||
```python
|
|
||||||
# 不同数据类型的查询操作
|
|
||||||
redis_operations = {
|
|
||||||
"string": "GET key",
|
|
||||||
"hash": "HGETALL key",
|
|
||||||
"list": "LRANGE key 0 -1",
|
|
||||||
"set": "SMEMBERS key",
|
|
||||||
"zset": "ZRANGE key 0 -1 WITHSCORES"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.3 Redis集群配置
|
|
||||||
```python
|
|
||||||
# Redis集群连接配置
|
|
||||||
redis_config = {
|
|
||||||
"cluster_name": "production-redis",
|
|
||||||
"nodes": [
|
|
||||||
{"host": "10.0.1.100", "port": 6379},
|
|
||||||
{"host": "10.0.1.101", "port": 6379},
|
|
||||||
{"host": "10.0.1.102", "port": 6379}
|
|
||||||
],
|
|
||||||
"password": "redis_password",
|
|
||||||
"socket_timeout": 3,
|
|
||||||
"socket_connect_timeout": 3,
|
|
||||||
"max_connections_per_node": 16,
|
|
||||||
"skip_full_coverage_check": True,
|
|
||||||
"decode_responses": True
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5. 数据迁移和版本管理
|
|
||||||
|
|
||||||
### 5.1 数据库版本控制
|
|
||||||
|
|
||||||
#### 5.1.1 版本信息表
|
|
||||||
```sql
|
|
||||||
CREATE TABLE database_version (
|
|
||||||
version TEXT PRIMARY KEY,
|
|
||||||
applied_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
description TEXT
|
|
||||||
);
|
|
||||||
|
|
||||||
INSERT INTO database_version (version, description)
|
|
||||||
VALUES ('1.0.0', '初始数据库结构');
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 5.1.2 迁移脚本示例
|
|
||||||
```sql
|
|
||||||
-- 版本1.0.0 -> 1.1.0 迁移脚本
|
|
||||||
-- 添加Redis配置支持
|
|
||||||
ALTER TABLE config_groups
|
|
||||||
ADD COLUMN redis_config TEXT;
|
|
||||||
|
|
||||||
INSERT INTO database_version (version, description)
|
|
||||||
VALUES ('1.1.0', '添加Redis配置支持');
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.2 数据备份策略
|
|
||||||
|
|
||||||
#### 5.2.1 SQLite备份
|
|
||||||
```bash
|
|
||||||
# 完整备份
|
|
||||||
cp config_groups.db config_groups_backup_$(date +%Y%m%d_%H%M%S).db
|
|
||||||
|
|
||||||
# 增量备份(基于时间戳)
|
|
||||||
sqlite3 config_groups.db "
|
|
||||||
SELECT * FROM config_groups
|
|
||||||
WHERE updated_at > '2024-08-05 00:00:00';" > incremental_backup.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 5.2.2 备份恢复
|
|
||||||
```bash
|
|
||||||
# 恢复完整备份
|
|
||||||
cp config_groups_backup_20240805_100000.db config_groups.db
|
|
||||||
|
|
||||||
# 恢复增量数据
|
|
||||||
sqlite3 config_groups.db < incremental_backup.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
## 6. 性能优化
|
|
||||||
|
|
||||||
### 6.1 SQLite优化
|
|
||||||
|
|
||||||
#### 6.1.1 配置优化
|
|
||||||
```sql
|
|
||||||
-- SQLite性能优化设置
|
|
||||||
PRAGMA journal_mode = WAL; -- 写前日志模式
|
|
||||||
PRAGMA synchronous = NORMAL; -- 平衡性能和安全
|
|
||||||
PRAGMA cache_size = 10000; -- 缓存页面数量
|
|
||||||
PRAGMA temp_store = memory; -- 临时表存储在内存
|
|
||||||
PRAGMA mmap_size = 268435456; -- 内存映射大小(256MB)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 6.1.2 查询优化
|
|
||||||
```sql
|
|
||||||
-- 分页查询优化
|
|
||||||
SELECT * FROM query_history
|
|
||||||
WHERE created_at >= ?
|
|
||||||
ORDER BY created_at DESC
|
|
||||||
LIMIT ? OFFSET ?;
|
|
||||||
|
|
||||||
-- 统计查询优化
|
|
||||||
SELECT query_type, COUNT(*), AVG(execution_time)
|
|
||||||
FROM query_history
|
|
||||||
WHERE created_at >= DATE('now', '-30 days')
|
|
||||||
GROUP BY query_type;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.2 连接池优化
|
|
||||||
|
|
||||||
#### 6.2.1 Cassandra连接池
|
|
||||||
```python
|
|
||||||
# 连接池配置
|
|
||||||
cluster = Cluster(
|
|
||||||
hosts=['10.0.1.100', '10.0.1.101'],
|
|
||||||
port=9042,
|
|
||||||
load_balancing_policy=DCAwareRoundRobinPolicy(local_dc='datacenter1'),
|
|
||||||
default_retry_policy=RetryPolicy(),
|
|
||||||
compression=True,
|
|
||||||
protocol_version=4
|
|
||||||
)
|
|
||||||
|
|
||||||
# 会话池管理
|
|
||||||
session_pool = cluster.connect()
|
|
||||||
session_pool.default_timeout = 30
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 6.2.2 Redis连接池
|
|
||||||
```python
|
|
||||||
# Redis集群连接池
|
|
||||||
from rediscluster import RedisCluster
|
|
||||||
|
|
||||||
redis_cluster = RedisCluster(
|
|
||||||
startup_nodes=[
|
|
||||||
{"host": "10.0.1.100", "port": 6379},
|
|
||||||
{"host": "10.0.1.101", "port": 6379}
|
|
||||||
],
|
|
||||||
password="redis_password",
|
|
||||||
socket_timeout=3,
|
|
||||||
socket_connect_timeout=3,
|
|
||||||
max_connections_per_node=16,
|
|
||||||
skip_full_coverage_check=True
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 7. 数据安全
|
|
||||||
|
|
||||||
### 7.1 敏感数据处理
|
|
||||||
|
|
||||||
#### 7.1.1 密码加密
|
|
||||||
```python
|
|
||||||
# 配置中的密码加密存储
|
|
||||||
import base64
|
|
||||||
from cryptography.fernet import Fernet
|
|
||||||
|
|
||||||
def encrypt_password(password, key):
|
|
||||||
"""加密密码"""
|
|
||||||
f = Fernet(key)
|
|
||||||
encrypted_password = f.encrypt(password.encode())
|
|
||||||
return base64.b64encode(encrypted_password).decode()
|
|
||||||
|
|
||||||
def decrypt_password(encrypted_password, key):
|
|
||||||
"""解密密码"""
|
|
||||||
f = Fernet(key)
|
|
||||||
decoded_password = base64.b64decode(encrypted_password.encode())
|
|
||||||
return f.decrypt(decoded_password).decode()
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 7.1.2 数据脱敏
|
|
||||||
```python
|
|
||||||
def mask_sensitive_data(config):
|
|
||||||
"""配置数据脱敏显示"""
|
|
||||||
masked_config = config.copy()
|
|
||||||
if 'password' in masked_config:
|
|
||||||
masked_config['password'] = '***masked***'
|
|
||||||
return masked_config
|
|
||||||
```
|
|
||||||
|
|
||||||
### 7.2 访问控制
|
|
||||||
|
|
||||||
#### 7.2.1 数据库权限
|
|
||||||
```sql
|
|
||||||
-- SQLite访问控制(文件系统级别)
|
|
||||||
chmod 600 config_groups.db -- 仅所有者可读写
|
|
||||||
chown app:app config_groups.db -- 设置正确的所有者
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 7.2.2 网络安全
|
|
||||||
```python
|
|
||||||
# Cassandra SSL连接
|
|
||||||
from cassandra.auth import PlainTextAuthProvider
|
|
||||||
from ssl import SSLContext, PROTOCOL_TLS
|
|
||||||
|
|
||||||
ssl_context = SSLContext(PROTOCOL_TLS)
|
|
||||||
auth_provider = PlainTextAuthProvider(username='app_user', password='password')
|
|
||||||
|
|
||||||
cluster = Cluster(
|
|
||||||
['10.0.1.100'],
|
|
||||||
ssl_context=ssl_context,
|
|
||||||
auth_provider=auth_provider
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 8. 监控和维护
|
|
||||||
|
|
||||||
### 8.1 数据库监控
|
|
||||||
|
|
||||||
#### 8.1.1 SQLite监控
|
|
||||||
```sql
|
|
||||||
-- 数据库大小监控
|
|
||||||
SELECT
|
|
||||||
(page_count * page_size) / 1024 / 1024 as db_size_mb
|
|
||||||
FROM pragma_page_count(), pragma_page_size();
|
|
||||||
|
|
||||||
-- 表大小统计
|
|
||||||
SELECT
|
|
||||||
name,
|
|
||||||
COUNT(*) as row_count
|
|
||||||
FROM sqlite_master sm
|
|
||||||
JOIN (
|
|
||||||
SELECT 'config_groups' as name, COUNT(*) as cnt FROM config_groups
|
|
||||||
UNION ALL
|
|
||||||
SELECT 'query_history' as name, COUNT(*) as cnt FROM query_history
|
|
||||||
UNION ALL
|
|
||||||
SELECT 'query_logs' as name, COUNT(*) as cnt FROM query_logs
|
|
||||||
) t ON sm.name = t.name;
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 8.1.2 连接监控
|
|
||||||
```python
|
|
||||||
# 连接健康检查
|
|
||||||
def check_database_health():
|
|
||||||
"""检查数据库连接健康状态"""
|
|
||||||
health_status = {
|
|
||||||
"sqlite": check_sqlite_health(),
|
|
||||||
"cassandra": check_cassandra_health(),
|
|
||||||
"redis": check_redis_health()
|
|
||||||
}
|
|
||||||
return health_status
|
|
||||||
|
|
||||||
def check_sqlite_health():
|
|
||||||
"""检查SQLite健康状态"""
|
|
||||||
try:
|
|
||||||
conn = sqlite3.connect('config_groups.db')
|
|
||||||
cursor = conn.cursor()
|
|
||||||
cursor.execute("SELECT 1")
|
|
||||||
conn.close()
|
|
||||||
return {"status": "healthy", "timestamp": datetime.now()}
|
|
||||||
except Exception as e:
|
|
||||||
return {"status": "unhealthy", "error": str(e)}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 8.2 数据维护
|
|
||||||
|
|
||||||
#### 8.2.1 数据清理
|
|
||||||
```sql
|
|
||||||
-- 清理30天前的查询日志
|
|
||||||
DELETE FROM query_logs
|
|
||||||
WHERE created_at < DATE('now', '-30 days');
|
|
||||||
|
|
||||||
-- 清理大型历史记录的详细数据
|
|
||||||
UPDATE query_history
|
|
||||||
SET raw_results = NULL,
|
|
||||||
differences_data = NULL,
|
|
||||||
identical_data = NULL
|
|
||||||
WHERE created_at < DATE('now', '-90 days');
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 8.2.2 数据库维护
|
|
||||||
```sql
|
|
||||||
-- SQLite数据库优化
|
|
||||||
VACUUM; -- 重建数据库文件,回收空间
|
|
||||||
ANALYZE; -- 更新查询计划统计信息
|
|
||||||
PRAGMA optimize; -- 优化数据库性能
|
|
||||||
REINDEX; -- 重建所有索引
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**版本**: v1.0
|
|
||||||
**更新日期**: 2024-08-05
|
|
||||||
**维护者**: DataTools Pro Team
|
|
1427
docs/deployment.md
1427
docs/deployment.md
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,708 +0,0 @@
|
|||||||
# DataTools Pro 用户使用手册
|
|
||||||
|
|
||||||
## 1. 系统介绍
|
|
||||||
|
|
||||||
### 1.1 产品概述
|
|
||||||
DataTools Pro 是一个企业级数据处理与比对工具平台,专为数据工程师和运维人员设计,提供高效、精准、可视化的数据分析解决方案。
|
|
||||||
|
|
||||||
### 1.2 主要功能
|
|
||||||
- **Cassandra数据比对**: 支持生产环境与测试环境的数据差异分析
|
|
||||||
- **Redis集群比对**: 支持多种数据类型的Redis集群数据比对
|
|
||||||
- **分表查询**: 支持TWCS时间分表的智能查询
|
|
||||||
- **多主键查询**: 支持复合主键的批量查询
|
|
||||||
- **配置管理**: 可视化的数据库配置管理
|
|
||||||
- **查询历史**: 完整的查询历史记录和结果复用
|
|
||||||
- **实时日志**: 详细的查询执行日志和性能监控
|
|
||||||
|
|
||||||
### 1.3 系统要求
|
|
||||||
- **浏览器**: Chrome 61+, Firefox 60+, Safari 10.1+, Edge 16+
|
|
||||||
- **网络**: 能够访问目标Cassandra和Redis集群
|
|
||||||
- **权限**: 具备目标数据库的读取权限
|
|
||||||
|
|
||||||
## 2. 快速入门
|
|
||||||
|
|
||||||
### 2.1 访问系统
|
|
||||||
1. 打开浏览器,访问系统地址(如:http://your-domain.com)
|
|
||||||
2. 系统首页展示了可用的工具模块和功能介绍
|
|
||||||
3. 选择需要使用的工具进入相应的功能页面
|
|
||||||
|
|
||||||
### 2.2 界面概览
|
|
||||||
```
|
|
||||||
DataTools Pro 界面布局:
|
|
||||||
┌─────────────────────────────────────────────────────────────┐
|
|
||||||
│ 导航栏 │
|
|
||||||
│ DataTools Pro | 首页 | Cassandra比对 | Redis比对 │
|
|
||||||
├─────────────────────────────────────────────────────────────┤
|
|
||||||
│ 面包屑导航 │
|
|
||||||
│ 首页 > Cassandra数据比对工具 │
|
|
||||||
├─────────────────────────────────────────────────────────────┤
|
|
||||||
│ 配置管理面板 │ 查询操作面板 │
|
|
||||||
│ ┌─────────────────┐ │ ┌─────────────────────────────┐ │
|
|
||||||
│ │ • 配置组管理 │ │ │ • 查询模式选择 │ │
|
|
||||||
│ │ • 数据库配置 │ │ │ • Key值输入 │ │
|
|
||||||
│ │ • 查询历史 │ │ │ • 比对字段配置 │ │
|
|
||||||
│ │ • 查询日志 │ │ │ • 执行按钮 │ │
|
|
||||||
│ └─────────────────┘ │ └─────────────────────────────┘ │
|
|
||||||
├─────────────────────────────────────────────────────────────┤
|
|
||||||
│ 结果展示区域 │
|
|
||||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
|
||||||
│ │ • 统计信息卡片 │ │
|
|
||||||
│ │ • 差异详情 | 相同结果 | 原始数据 | 比较总结 │ │
|
|
||||||
│ │ • 分页导航和搜索过滤 │ │
|
|
||||||
│ └─────────────────────────────────────────────────────────┘ │
|
|
||||||
└─────────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.3 键盘快捷键
|
|
||||||
- `Ctrl/Cmd + H`: 返回首页
|
|
||||||
- `Ctrl/Cmd + 1`: 切换到Cassandra工具
|
|
||||||
- `Ctrl/Cmd + 2`: 切换到Redis工具
|
|
||||||
- `?`: 显示/隐藏快捷键提示
|
|
||||||
|
|
||||||
## 3. Cassandra数据比对工具
|
|
||||||
|
|
||||||
### 3.1 功能概述
|
|
||||||
Cassandra数据比对工具支持对生产环境和测试环境的Cassandra数据库进行精确的数据比对分析,支持单表查询、分表查询和多主键查询三种模式。
|
|
||||||
|
|
||||||
### 3.2 配置管理
|
|
||||||
|
|
||||||
#### 3.2.1 配置组管理
|
|
||||||
配置组功能允许您保存和管理常用的数据库配置,避免重复输入。
|
|
||||||
|
|
||||||
**创建配置组**:
|
|
||||||
1. 点击"保存配置组"按钮
|
|
||||||
2. 输入配置组名称和描述
|
|
||||||
3. 系统将保存当前的所有配置信息
|
|
||||||
4. 配置组创建成功后可在下拉列表中选择
|
|
||||||
|
|
||||||
**加载配置组**:
|
|
||||||
1. 在配置组下拉列表中选择已保存的配置组
|
|
||||||
2. 点击"加载"按钮
|
|
||||||
3. 系统将自动填充所有相关配置
|
|
||||||
|
|
||||||
**管理配置组**:
|
|
||||||
1. 点击"管理配置组"按钮
|
|
||||||
2. 查看所有已保存的配置组
|
|
||||||
3. 可以删除不需要的配置组
|
|
||||||
|
|
||||||
#### 3.2.2 数据库配置
|
|
||||||
|
|
||||||
**生产环境配置**:
|
|
||||||
```
|
|
||||||
配置项说明:
|
|
||||||
• 集群名称: Cassandra集群的标识名称
|
|
||||||
• 数据中心: 数据中心名称 (如: datacenter1)
|
|
||||||
• 主机地址: Cassandra节点地址列表 (支持多个,逗号分隔)
|
|
||||||
• 端口: 连接端口 (默认: 9042)
|
|
||||||
• 用户名: 数据库用户名 (可选)
|
|
||||||
• 密码: 数据库密码 (可选)
|
|
||||||
• 键空间: Keyspace名称
|
|
||||||
• 表名: 要查询的表名
|
|
||||||
```
|
|
||||||
|
|
||||||
**测试环境配置**:
|
|
||||||
配置项与生产环境相同,用于配置测试环境的Cassandra连接信息。
|
|
||||||
|
|
||||||
**一键导入功能**:
|
|
||||||
1. 点击"一键导入"按钮
|
|
||||||
2. 可以从生产环境配置快速复制到测试环境
|
|
||||||
3. 根据需要修改测试环境的特定配置
|
|
||||||
|
|
||||||
### 3.3 查询模式
|
|
||||||
|
|
||||||
#### 3.3.1 单表查询模式
|
|
||||||
默认的查询模式,适用于普通的Cassandra表查询。
|
|
||||||
|
|
||||||
**操作步骤**:
|
|
||||||
1. 确保分表模式开关处于关闭状态
|
|
||||||
2. 配置生产和测试环境的数据库连接
|
|
||||||
3. 设置查询参数
|
|
||||||
4. 输入要查询的Key值
|
|
||||||
5. 点击"执行查询比对"
|
|
||||||
|
|
||||||
#### 3.3.2 分表查询模式
|
|
||||||
适用于使用TWCS (Time Window Compaction Strategy) 分表策略的Cassandra表。
|
|
||||||
|
|
||||||
**启用分表模式**:
|
|
||||||
1. 打开"分表模式"开关
|
|
||||||
2. 分别配置生产和测试环境的分表参数
|
|
||||||
|
|
||||||
**分表参数配置**:
|
|
||||||
```
|
|
||||||
配置说明:
|
|
||||||
• 生产环境分表: 是否对生产环境启用分表查询
|
|
||||||
• 测试环境分表: 是否对测试环境启用分表查询
|
|
||||||
• 时间间隔(秒): 分表的时间间隔,默认604800秒(7天)
|
|
||||||
• 分表数量: 总分表数量,默认14张表
|
|
||||||
```
|
|
||||||
|
|
||||||
**分表算法说明**:
|
|
||||||
- 系统从Key中提取时间戳(删除所有非数字字符)
|
|
||||||
- 计算公式: `shard_index = timestamp // interval_seconds % table_count`
|
|
||||||
- 示例: Key为"wmid_1609459200",提取时间戳1609459200,计算得到分表索引
|
|
||||||
|
|
||||||
#### 3.3.3 多主键查询模式
|
|
||||||
支持复合主键的批量查询,适用于具有多个主键字段的表。
|
|
||||||
|
|
||||||
**配置多主键**:
|
|
||||||
1. 在"主键字段"中输入多个字段名,用逗号分隔
|
|
||||||
- 示例: `user_id,timestamp` 或 `docid,id`
|
|
||||||
2. 在查询Key值中输入复合主键值,每行一组,字段值用逗号分隔
|
|
||||||
- 示例:
|
|
||||||
```
|
|
||||||
1001,1609459200
|
|
||||||
1002,1609545600
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.4 查询配置
|
|
||||||
|
|
||||||
#### 3.4.1 基础查询参数
|
|
||||||
```
|
|
||||||
参数说明:
|
|
||||||
• 主键字段: 表的主键字段名,支持单个或多个字段
|
|
||||||
• 比对字段: 指定要比对的字段列表 (空表示全部字段)
|
|
||||||
• 排除字段: 指定要排除的字段列表
|
|
||||||
• 查询Key值: 要查询的主键值列表,每行一个
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.4.2 字段配置技巧
|
|
||||||
- **全字段比对**: 比对字段留空,系统将比对所有字段
|
|
||||||
- **指定字段比对**: 在比对字段中列出需要比对的字段名
|
|
||||||
- **排除字段**: 在排除字段中列出不需要比对的字段(如时间戳字段)
|
|
||||||
- **字段名格式**: 使用英文逗号分隔多个字段名
|
|
||||||
|
|
||||||
### 3.5 执行查询
|
|
||||||
|
|
||||||
#### 3.5.1 查询执行流程
|
|
||||||
1. **配置验证**: 系统验证数据库连接配置的完整性
|
|
||||||
2. **连接测试**: 尝试连接到生产和测试环境
|
|
||||||
3. **并行查询**: 同时执行生产和测试环境的数据查询
|
|
||||||
4. **数据比对**: 对查询结果进行字段级比对分析
|
|
||||||
5. **结果展示**: 生成详细的比对报告和统计信息
|
|
||||||
|
|
||||||
#### 3.5.2 执行过程监控
|
|
||||||
- 查询执行时显示进度指示器
|
|
||||||
- 实时显示执行状态和进度信息
|
|
||||||
- 可以通过查询日志查看详细的执行过程
|
|
||||||
|
|
||||||
### 3.6 结果分析
|
|
||||||
|
|
||||||
#### 3.6.1 统计信息卡片
|
|
||||||
查询完成后,系统会显示以下统计信息:
|
|
||||||
```
|
|
||||||
统计指标:
|
|
||||||
• 查询总数: 本次查询的Key总数量
|
|
||||||
• 生产记录数: 生产环境查询到的记录数
|
|
||||||
• 测试记录数: 测试环境查询到的记录数
|
|
||||||
• 差异数量: 发现的数据差异数量
|
|
||||||
• 相同数量: 数据完全相同的记录数量
|
|
||||||
• 匹配率: 数据匹配的百分比
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.6.2 结果选项卡
|
|
||||||
|
|
||||||
**差异详情选项卡**:
|
|
||||||
- 显示所有发现的数据差异
|
|
||||||
- 每条差异记录包含:
|
|
||||||
- Key信息 (支持复合主键显示)
|
|
||||||
- 字段名
|
|
||||||
- 生产环境值
|
|
||||||
- 测试环境值
|
|
||||||
- 差异类型说明
|
|
||||||
- 支持分页浏览和搜索过滤
|
|
||||||
|
|
||||||
**相同结果选项卡**:
|
|
||||||
- 显示数据完全相同的记录
|
|
||||||
- 包含生产和测试环境的完整字段值
|
|
||||||
- 可用于验证数据同步的正确性
|
|
||||||
|
|
||||||
**原始数据选项卡**:
|
|
||||||
- 显示生产和测试环境的原始查询数据
|
|
||||||
- 支持格式化和原始两种显示模式
|
|
||||||
- 提供数据导出功能
|
|
||||||
|
|
||||||
**比较总结选项卡**:
|
|
||||||
- 提供详细的数据质量分析报告
|
|
||||||
- 包含匹配率统计和字段级差异分析
|
|
||||||
- 给出数据改进建议
|
|
||||||
|
|
||||||
#### 3.6.3 分表查询信息
|
|
||||||
当使用分表查询模式时,结果页面会显示额外的分表信息:
|
|
||||||
```
|
|
||||||
分表信息包含:
|
|
||||||
• 分表映射关系: 每个Key对应的分表名称
|
|
||||||
• 分表统计: 使用的分表列表和分布情况
|
|
||||||
• 时间戳提取: 时间戳提取成功率
|
|
||||||
• 失败Key列表: 时间戳提取失败的Key
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.7 查询历史管理
|
|
||||||
|
|
||||||
#### 3.7.1 保存查询历史
|
|
||||||
1. 查询执行完成后,点击"保存历史"按钮
|
|
||||||
2. 输入历史记录名称和描述
|
|
||||||
3. 系统将保存完整的查询配置和结果数据
|
|
||||||
|
|
||||||
#### 3.7.2 查看查询历史
|
|
||||||
1. 点击"查询历史"按钮
|
|
||||||
2. 浏览所有已保存的历史记录
|
|
||||||
3. 可以查看历史记录的详细信息:
|
|
||||||
- 查询配置
|
|
||||||
- 执行时间
|
|
||||||
- 结果统计
|
|
||||||
- 完整的差异和相同数据
|
|
||||||
|
|
||||||
#### 3.7.3 历史记录操作
|
|
||||||
- **加载历史**: 从历史记录恢复查询配置
|
|
||||||
- **查看结果**: 查看历史查询的完整结果
|
|
||||||
- **删除记录**: 删除不需要的历史记录
|
|
||||||
|
|
||||||
### 3.8 查询日志
|
|
||||||
|
|
||||||
#### 3.8.1 日志功能
|
|
||||||
查询日志记录了查询执行过程中的所有关键信息:
|
|
||||||
- SQL语句执行记录
|
|
||||||
- 数据库连接状态
|
|
||||||
- 查询耗时统计
|
|
||||||
- 错误和警告信息
|
|
||||||
|
|
||||||
#### 3.8.2 日志查看
|
|
||||||
1. 点击"查询日志"按钮
|
|
||||||
2. 可以按日志级别过滤:
|
|
||||||
- INFO: 一般信息
|
|
||||||
- WARNING: 警告信息
|
|
||||||
- ERROR: 错误信息
|
|
||||||
3. 支持按时间范围查看日志
|
|
||||||
|
|
||||||
#### 3.8.3 日志管理
|
|
||||||
- **实时刷新**: 实时查看最新的日志信息
|
|
||||||
- **清空日志**: 清除所有历史日志记录
|
|
||||||
- **日志导出**: 导出日志用于分析和故障排查
|
|
||||||
|
|
||||||
## 4. Redis集群比对工具
|
|
||||||
|
|
||||||
### 4.1 功能概述
|
|
||||||
Redis集群比对工具专门用于对比两个Redis集群之间的数据差异,支持所有Redis数据类型,提供随机采样和指定Key两种查询模式。
|
|
||||||
|
|
||||||
### 4.2 Redis集群配置
|
|
||||||
|
|
||||||
#### 4.2.1 集群配置参数
|
|
||||||
```
|
|
||||||
配置项说明:
|
|
||||||
• 集群名称: Redis集群的标识名称
|
|
||||||
• Redis节点: 集群节点列表,支持添加多个节点
|
|
||||||
- 主机地址: Redis节点IP地址
|
|
||||||
- 端口: Redis端口 (默认: 6379)
|
|
||||||
• 密码: Redis认证密码 (可选)
|
|
||||||
• 连接超时: Socket连接超时时间 (默认: 3秒)
|
|
||||||
• 建立超时: Socket建立连接超时时间 (默认: 3秒)
|
|
||||||
• 最大连接数: 每个节点的最大连接数 (默认: 16)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 4.2.2 集群节点管理
|
|
||||||
**添加节点**:
|
|
||||||
1. 在节点配置区域点击"添加节点"按钮
|
|
||||||
2. 输入节点的IP地址和端口
|
|
||||||
3. 可以添加多个节点以支持集群模式
|
|
||||||
|
|
||||||
**删除节点**:
|
|
||||||
1. 点击节点右侧的删除按钮
|
|
||||||
2. 确认删除操作
|
|
||||||
|
|
||||||
**测试连接**:
|
|
||||||
1. 配置完成后点击"测试连接"按钮
|
|
||||||
2. 系统将测试与集群的连接状态
|
|
||||||
3. 显示连接结果和节点状态
|
|
||||||
|
|
||||||
### 4.3 查询模式
|
|
||||||
|
|
||||||
#### 4.3.1 随机采样模式
|
|
||||||
适用于对Redis集群进行数据质量抽查和整体数据一致性验证。
|
|
||||||
|
|
||||||
**配置参数**:
|
|
||||||
```
|
|
||||||
参数说明:
|
|
||||||
• 采样数量: 随机采样的Key数量 (1-10000)
|
|
||||||
• Key模式: Key的匹配模式 (默认: *)
|
|
||||||
• 源集群: 选择从哪个集群获取Key列表
|
|
||||||
```
|
|
||||||
|
|
||||||
**操作步骤**:
|
|
||||||
1. 选择"随机采样模式"
|
|
||||||
2. 设置采样数量 (建议100-1000)
|
|
||||||
3. 设置Key模式 (如: user:*, session:*, 或 *)
|
|
||||||
4. 选择源集群 (通常选择数据较完整的集群)
|
|
||||||
5. 点击"开始Redis数据比较"
|
|
||||||
|
|
||||||
#### 4.3.2 指定Key模式
|
|
||||||
适用于对特定Key进行精确的数据比对。
|
|
||||||
|
|
||||||
**操作步骤**:
|
|
||||||
1. 选择"指定Key模式"
|
|
||||||
2. 在Key列表文本框中输入要查询的Key
|
|
||||||
3. 每行一个Key,支持大批量Key查询
|
|
||||||
4. 建议单次不超过1000个Key
|
|
||||||
5. 点击"开始Redis数据比较"
|
|
||||||
|
|
||||||
**Key输入示例**:
|
|
||||||
```
|
|
||||||
user:1001
|
|
||||||
user:1002
|
|
||||||
session:abc123
|
|
||||||
cache:product:12345
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.4 数据类型支持
|
|
||||||
|
|
||||||
#### 4.4.1 支持的Redis数据类型
|
|
||||||
Redis集群比对工具支持以下数据类型的比对:
|
|
||||||
- **String**: 字符串类型,包括JSON字符串
|
|
||||||
- **Hash**: 哈希表,支持字段级比对
|
|
||||||
- **List**: 列表,支持顺序和内容比对
|
|
||||||
- **Set**: 集合,支持成员比对(顺序无关)
|
|
||||||
- **Sorted Set**: 有序集合,支持成员和分数比对
|
|
||||||
|
|
||||||
#### 4.4.2 数据比对逻辑
|
|
||||||
```
|
|
||||||
比对规则:
|
|
||||||
• String类型: 直接比较字符串值
|
|
||||||
• Hash类型: 逐字段比较,显示字段级差异
|
|
||||||
• List类型: 比较列表长度和元素内容
|
|
||||||
• Set类型: 比较集合成员,忽略顺序
|
|
||||||
• ZSet类型: 比较成员和分数的对应关系
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.5 结果分析
|
|
||||||
|
|
||||||
#### 4.5.1 统计信息
|
|
||||||
Redis比对完成后显示以下统计信息:
|
|
||||||
```
|
|
||||||
统计指标:
|
|
||||||
• 查询总数: 本次查询的Key总数
|
|
||||||
• 集群1找到: 集群1中存在的Key数量
|
|
||||||
• 集群2找到: 集群2中存在的Key数量
|
|
||||||
• 差异数量: 值不同的Key数量
|
|
||||||
• 相同数量: 值完全相同的Key数量
|
|
||||||
• 缺失数量: 在某个集群中不存在的Key数量
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 4.5.2 结果选项卡
|
|
||||||
|
|
||||||
**差异详情选项卡**:
|
|
||||||
- 显示值不同的Key详细信息
|
|
||||||
- 包含Key名称、数据类型、两个集群的值
|
|
||||||
- 对于复杂数据类型,显示具体的差异字段
|
|
||||||
|
|
||||||
**相同结果选项卡**:
|
|
||||||
- 显示完全相同的Key和值
|
|
||||||
- 用于验证数据同步的正确性
|
|
||||||
|
|
||||||
**缺失数据选项卡**:
|
|
||||||
- 显示在某个集群中不存在的Key
|
|
||||||
- 分为"集群1缺失"和"集群2缺失"两个部分
|
|
||||||
|
|
||||||
**原生数据选项卡**:
|
|
||||||
- 显示两个集群的原始查询数据
|
|
||||||
- 支持格式化和原始数据两种视图
|
|
||||||
- 提供数据导出功能
|
|
||||||
|
|
||||||
**比较总结选项卡**:
|
|
||||||
- 生成性能报告和数据质量分析
|
|
||||||
- 包含集群连接状态和响应时间统计
|
|
||||||
- 提供数据一致性改进建议
|
|
||||||
|
|
||||||
### 4.6 性能监控
|
|
||||||
|
|
||||||
#### 4.6.1 集群状态监控
|
|
||||||
系统实时监控Redis集群的运行状态:
|
|
||||||
```
|
|
||||||
监控指标:
|
|
||||||
• 连接状态: 显示每个节点的连接状态
|
|
||||||
• 平均响应时间: 集群的平均响应时间
|
|
||||||
• 节点状态: 各个节点的运行状态
|
|
||||||
• 数据传输量: 本次查询的数据传输量
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 4.6.2 性能统计
|
|
||||||
查询完成后显示性能统计信息:
|
|
||||||
- 总执行时间
|
|
||||||
- 每秒处理Key数量
|
|
||||||
- 数据传输量统计
|
|
||||||
- 集群负载分析
|
|
||||||
|
|
||||||
## 5. 高级功能
|
|
||||||
|
|
||||||
### 5.1 配置导入导出
|
|
||||||
|
|
||||||
#### 5.1.1 配置导出
|
|
||||||
1. 在任何工具页面点击"导出配置"按钮
|
|
||||||
2. 系统将生成包含所有配置信息的JSON文件
|
|
||||||
3. 文件包含数据库配置、查询参数、分表配置等
|
|
||||||
|
|
||||||
#### 5.1.2 配置导入
|
|
||||||
1. 点击"一键导入"或相关导入按钮
|
|
||||||
2. 选择导入方式:
|
|
||||||
- **文本粘贴**: 直接粘贴YAML或JSON配置
|
|
||||||
- **文件上传**: 上传配置文件或拖拽文件
|
|
||||||
3. 预览配置内容
|
|
||||||
4. 确认导入
|
|
||||||
|
|
||||||
#### 5.1.3 支持的配置格式
|
|
||||||
**YAML格式示例**:
|
|
||||||
```yaml
|
|
||||||
clusterName: "production-cluster"
|
|
||||||
clusterAddress: "10.0.1.100:9042,10.0.1.101:9042"
|
|
||||||
keyspace: "production_ks"
|
|
||||||
table: "user_data"
|
|
||||||
username: "cassandra_user"
|
|
||||||
password: "password"
|
|
||||||
```
|
|
||||||
|
|
||||||
**JSON格式示例**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"cluster_name": "production-cluster",
|
|
||||||
"hosts": ["10.0.1.100", "10.0.1.101"],
|
|
||||||
"port": 9042,
|
|
||||||
"keyspace": "production_ks",
|
|
||||||
"table": "user_data"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.2 数据导出功能
|
|
||||||
|
|
||||||
#### 5.2.1 结果导出格式
|
|
||||||
支持多种数据导出格式:
|
|
||||||
- **Excel格式 (.xlsx)**: 适合数据分析和报告
|
|
||||||
- **CSV格式 (.csv)**: 通用的数据交换格式
|
|
||||||
- **JSON格式 (.json)**: 适合程序处理
|
|
||||||
- **HTML报告**: 可打印的详细报告
|
|
||||||
|
|
||||||
#### 5.2.2 导出内容选择
|
|
||||||
可以选择导出以下内容:
|
|
||||||
- 差异数据详情
|
|
||||||
- 相同数据记录
|
|
||||||
- 原始查询数据
|
|
||||||
- 统计分析报告
|
|
||||||
- 完整的比对结果
|
|
||||||
|
|
||||||
#### 5.2.3 导出操作步骤
|
|
||||||
1. 在结果页面点击"导出结果"按钮
|
|
||||||
2. 选择导出格式和内容范围
|
|
||||||
3. 系统生成并下载导出文件
|
|
||||||
4. 文件名包含时间戳,便于管理
|
|
||||||
|
|
||||||
### 5.3 批量操作
|
|
||||||
|
|
||||||
#### 5.3.1 批量Key导入
|
|
||||||
支持大批量Key的导入和处理:
|
|
||||||
- **文件导入**: 支持.txt, .csv, .xlsx格式
|
|
||||||
- **粘贴导入**: 直接粘贴Key列表
|
|
||||||
- **去重处理**: 自动去除重复的Key
|
|
||||||
- **格式验证**: 验证Key格式的正确性
|
|
||||||
|
|
||||||
#### 5.3.2 批量查询优化
|
|
||||||
系统对批量查询进行了优化:
|
|
||||||
- **分批处理**: 大量Key自动分批查询
|
|
||||||
- **并行执行**: 多线程并行处理提高效率
|
|
||||||
- **进度显示**: 实时显示查询进度
|
|
||||||
- **错误处理**: 单个Key失败不影响整体查询
|
|
||||||
|
|
||||||
### 5.4 数据过滤和搜索
|
|
||||||
|
|
||||||
#### 5.4.1 实时搜索
|
|
||||||
在结果页面提供实时搜索功能:
|
|
||||||
- **Key搜索**: 根据Key名称搜索
|
|
||||||
- **字段搜索**: 根据字段名搜索
|
|
||||||
- **值搜索**: 根据字段值搜索
|
|
||||||
- **模糊匹配**: 支持部分匹配和通配符
|
|
||||||
|
|
||||||
#### 5.4.2 高级过滤
|
|
||||||
提供多种过滤条件:
|
|
||||||
- **差异类型过滤**: 按差异类型筛选
|
|
||||||
- **字段名过滤**: 按特定字段筛选
|
|
||||||
- **数据类型过滤**: 按Redis数据类型筛选
|
|
||||||
- **时间范围过滤**: 按查询时间筛选
|
|
||||||
|
|
||||||
## 6. 故障排查
|
|
||||||
|
|
||||||
### 6.1 常见问题
|
|
||||||
|
|
||||||
#### 6.1.1 连接问题
|
|
||||||
**问题**: 无法连接到Cassandra/Redis集群
|
|
||||||
**解决方案**:
|
|
||||||
1. 检查网络连通性: `ping <host>`
|
|
||||||
2. 检查端口是否开放: `telnet <host> <port>`
|
|
||||||
3. 验证用户名和密码
|
|
||||||
4. 确认防火墙设置
|
|
||||||
5. 检查集群状态
|
|
||||||
|
|
||||||
**问题**: 连接超时
|
|
||||||
**解决方案**:
|
|
||||||
1. 增加连接超时时间
|
|
||||||
2. 检查网络延迟
|
|
||||||
3. 确认集群负载情况
|
|
||||||
4. 检查DNS解析
|
|
||||||
|
|
||||||
#### 6.1.2 查询问题
|
|
||||||
**问题**: 查询结果为空
|
|
||||||
**解决方案**:
|
|
||||||
1. 验证Key是否存在于数据库中
|
|
||||||
2. 检查表名和键空间是否正确
|
|
||||||
3. 确认主键字段名拼写
|
|
||||||
4. 检查数据权限
|
|
||||||
|
|
||||||
**问题**: 分表查询失败
|
|
||||||
**解决方案**:
|
|
||||||
1. 验证分表参数配置
|
|
||||||
2. 检查Key中是否包含时间戳
|
|
||||||
3. 确认分表命名规则
|
|
||||||
4. 验证分表数量设置
|
|
||||||
|
|
||||||
#### 6.1.3 性能问题
|
|
||||||
**问题**: 查询速度慢
|
|
||||||
**解决方案**:
|
|
||||||
1. 减少单次查询的Key数量
|
|
||||||
2. 优化网络连接
|
|
||||||
3. 检查数据库负载
|
|
||||||
4. 使用更高效的查询模式
|
|
||||||
|
|
||||||
**问题**: 内存使用过高
|
|
||||||
**解决方案**:
|
|
||||||
1. 分批处理大量数据
|
|
||||||
2. 减少比对字段数量
|
|
||||||
3. 清理历史查询记录
|
|
||||||
4. 重启浏览器释放内存
|
|
||||||
|
|
||||||
### 6.2 错误信息解读
|
|
||||||
|
|
||||||
#### 6.2.1 连接错误
|
|
||||||
```
|
|
||||||
错误类型及解决方案:
|
|
||||||
|
|
||||||
• "Connection refused"
|
|
||||||
- 检查目标主机是否可达
|
|
||||||
- 确认端口是否正确
|
|
||||||
- 检查防火墙设置
|
|
||||||
|
|
||||||
• "Authentication failed"
|
|
||||||
- 验证用户名和密码
|
|
||||||
- 检查用户权限
|
|
||||||
- 确认认证方式
|
|
||||||
|
|
||||||
• "Timeout occurred"
|
|
||||||
- 增加超时时间设置
|
|
||||||
- 检查网络延迟
|
|
||||||
- 确认集群健康状态
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 6.2.2 查询错误
|
|
||||||
```
|
|
||||||
错误类型及解决方案:
|
|
||||||
|
|
||||||
• "Keyspace not found"
|
|
||||||
- 检查键空间名称拼写
|
|
||||||
- 确认键空间是否存在
|
|
||||||
- 验证用户访问权限
|
|
||||||
|
|
||||||
• "Table not found"
|
|
||||||
- 检查表名拼写
|
|
||||||
- 确认表是否存在于指定键空间
|
|
||||||
- 验证表访问权限
|
|
||||||
|
|
||||||
• "Invalid primary key"
|
|
||||||
- 检查主键字段名
|
|
||||||
- 确认主键值格式
|
|
||||||
- 验证复合主键配置
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.3 性能优化建议
|
|
||||||
|
|
||||||
#### 6.3.1 查询优化
|
|
||||||
1. **合理设置批次大小**: 建议单次查询1000个Key以内
|
|
||||||
2. **选择合适的比对字段**: 只比对必要的字段
|
|
||||||
3. **使用分表查询**: 对于大表使用分表查询提高效率
|
|
||||||
4. **定期清理历史数据**: 删除不需要的历史记录和日志
|
|
||||||
|
|
||||||
#### 6.3.2 网络优化
|
|
||||||
1. **就近部署**: 在靠近数据库的网络环境中使用工具
|
|
||||||
2. **稳定网络**: 确保网络连接稳定,避免频繁重连
|
|
||||||
3. **并发控制**: 合理设置并发连接数
|
|
||||||
4. **压缩传输**: 启用数据传输压缩
|
|
||||||
|
|
||||||
#### 6.3.3 数据库优化
|
|
||||||
1. **读取权限**: 确保使用只读权限账户
|
|
||||||
2. **索引优化**: 确保查询字段有适当的索引
|
|
||||||
3. **分区策略**: 合理设计Cassandra分区键
|
|
||||||
4. **连接池**: 使用连接池避免频繁建立连接
|
|
||||||
|
|
||||||
## 7. 最佳实践
|
|
||||||
|
|
||||||
### 7.1 数据比对策略
|
|
||||||
|
|
||||||
#### 7.1.1 渐进式比对
|
|
||||||
1. **小范围测试**: 先用少量Key测试配置和连接
|
|
||||||
2. **分批比对**: 将大量数据分批进行比对
|
|
||||||
3. **重点字段**: 优先比对关键业务字段
|
|
||||||
4. **定期验证**: 建立定期数据比对的流程
|
|
||||||
|
|
||||||
#### 7.1.2 差异分析方法
|
|
||||||
1. **分类分析**: 按字段类型分类分析差异
|
|
||||||
2. **趋势观察**: 观察差异随时间的变化趋势
|
|
||||||
3. **根因分析**: 深入分析差异产生的根本原因
|
|
||||||
4. **修复验证**: 修复后重新验证数据一致性
|
|
||||||
|
|
||||||
### 7.2 配置管理最佳实践
|
|
||||||
|
|
||||||
#### 7.2.1 配置组织
|
|
||||||
1. **命名规范**: 使用有意义的配置组名称
|
|
||||||
2. **环境区分**: 清楚标识生产、测试、开发环境
|
|
||||||
3. **版本管理**: 记录配置变更历史
|
|
||||||
4. **权限控制**: 限制敏感配置的访问权限
|
|
||||||
|
|
||||||
#### 7.2.2 安全考虑
|
|
||||||
1. **密码管理**: 避免在配置中硬编码密码
|
|
||||||
2. **权限最小化**: 使用最小必要权限的数据库账户
|
|
||||||
3. **网络隔离**: 在安全的网络环境中使用工具
|
|
||||||
4. **审计日志**: 记录重要操作的审计日志
|
|
||||||
|
|
||||||
### 7.3 团队协作
|
|
||||||
|
|
||||||
#### 7.3.1 工作流程标准化
|
|
||||||
1. **操作文档**: 制定标准的操作流程文档
|
|
||||||
2. **结果记录**: 建立查询结果的记录和归档制度
|
|
||||||
3. **问题跟踪**: 建立差异问题的跟踪和解决流程
|
|
||||||
4. **知识分享**: 定期分享使用经验和最佳实践
|
|
||||||
|
|
||||||
#### 7.3.2 质量控制
|
|
||||||
1. **双人验证**: 重要数据比对结果需要双人确认
|
|
||||||
2. **结果审核**: 建立结果审核和批准流程
|
|
||||||
3. **变更控制**: 对配置变更进行严格控制
|
|
||||||
4. **定期回顾**: 定期回顾和改进数据比对流程
|
|
||||||
|
|
||||||
## 8. 技术支持
|
|
||||||
|
|
||||||
### 8.1 获取帮助
|
|
||||||
- **在线文档**: 访问系统内置的帮助文档
|
|
||||||
- **技术支持**: 联系技术支持团队
|
|
||||||
- **社区论坛**: 参与用户社区讨论
|
|
||||||
- **培训资源**: 获取相关的培训材料
|
|
||||||
|
|
||||||
### 8.2 反馈和建议
|
|
||||||
我们欢迎您的反馈和建议:
|
|
||||||
- **功能建议**: 提出新功能需求
|
|
||||||
- **问题报告**: 报告使用中遇到的问题
|
|
||||||
- **改进意见**: 提供用户体验改进建议
|
|
||||||
- **文档更新**: 建议文档内容的改进
|
|
||||||
|
|
||||||
### 8.3 版本更新
|
|
||||||
- **更新通知**: 关注系统版本更新通知
|
|
||||||
- **新功能**: 了解新版本的功能特性
|
|
||||||
- **兼容性**: 注意版本兼容性说明
|
|
||||||
- **升级指南**: 遵循版本升级指南
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**DataTools Pro v2.0 用户使用手册**
|
|
||||||
**更新日期**: 2024-08-05
|
|
||||||
**版权所有**: DataTools Pro Team
|
|
@@ -68,6 +68,32 @@ def setup_routes(app, query_log_collector):
|
|||||||
return render_template('redis_test.html')
|
return render_template('redis_test.html')
|
||||||
|
|
||||||
# 基础API
|
# 基础API
|
||||||
|
@app.route('/api/health')
|
||||||
|
def health_check():
|
||||||
|
"""健康检查端点,用于Docker健康检查和服务监控"""
|
||||||
|
try:
|
||||||
|
# 检查应用基本状态
|
||||||
|
current_time = datetime.now().isoformat()
|
||||||
|
|
||||||
|
# 简单的数据库连接检查(可选)
|
||||||
|
from .database import ensure_database
|
||||||
|
db_status = ensure_database()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'status': 'healthy',
|
||||||
|
'timestamp': current_time,
|
||||||
|
'service': 'BigDataTool',
|
||||||
|
'version': '2.0',
|
||||||
|
'database': 'ok' if db_status else 'warning'
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"健康检查失败: {str(e)}")
|
||||||
|
return jsonify({
|
||||||
|
'status': 'unhealthy',
|
||||||
|
'error': str(e),
|
||||||
|
'timestamp': datetime.now().isoformat()
|
||||||
|
}), 503
|
||||||
|
|
||||||
@app.route('/api/default-config')
|
@app.route('/api/default-config')
|
||||||
def get_default_config():
|
def get_default_config():
|
||||||
return jsonify(DEFAULT_CONFIG)
|
return jsonify(DEFAULT_CONFIG)
|
||||||
|
@@ -83,16 +83,16 @@ def create_connection(config):
|
|||||||
|
|
||||||
使用示例:
|
使用示例:
|
||||||
config = {
|
config = {
|
||||||
'hosts': ['192.168.1.100', '192.168.1.101'],
|
'hosts': ['127.0.0.1'],
|
||||||
'port': 9042,
|
'port': 9042,
|
||||||
'username': 'cassandra',
|
'username': 'cassandra',
|
||||||
'password': 'password',
|
'password': 'password',
|
||||||
'keyspace': 'my_keyspace',
|
'keyspace': 'example_keyspace',
|
||||||
'datacenter': 'dc1'
|
'datacenter': 'dc1'
|
||||||
}
|
}
|
||||||
cluster, session = create_connection(config)
|
cluster, session = create_connection(config)
|
||||||
if session:
|
if session:
|
||||||
result = session.execute("SELECT * FROM my_table LIMIT 10")
|
result = session.execute("SELECT * FROM example_table LIMIT 10")
|
||||||
cluster.shutdown()
|
cluster.shutdown()
|
||||||
"""
|
"""
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
|
@@ -279,6 +279,11 @@ def compare_json_arrays(array1, array2):
|
|||||||
|
|
||||||
def format_json_for_display(value):
|
def format_json_for_display(value):
|
||||||
"""格式化JSON用于显示"""
|
"""格式化JSON用于显示"""
|
||||||
|
# 处理None值
|
||||||
|
if value is None:
|
||||||
|
return "null"
|
||||||
|
|
||||||
|
# 处理非字符串类型
|
||||||
if not isinstance(value, str):
|
if not isinstance(value, str):
|
||||||
return str(value)
|
return str(value)
|
||||||
|
|
||||||
|
@@ -65,7 +65,7 @@ def get_redis_value_with_type(redis_client, key):
|
|||||||
- 数据异常:记录警告并提供基本信息
|
- 数据异常:记录警告并提供基本信息
|
||||||
|
|
||||||
示例:
|
示例:
|
||||||
>>> result = get_redis_value_with_type(client, "user:1001")
|
>>> result = get_redis_value_with_type(client, "user:example")
|
||||||
>>> print(result['type']) # 'string'
|
>>> print(result['type']) # 'string'
|
||||||
>>> print(result['value']) # 'John Doe'
|
>>> print(result['value']) # 'John Doe'
|
||||||
>>> print(result['exists']) # True
|
>>> print(result['exists']) # True
|
||||||
|
779
static/js/app.js
779
static/js/app.js
@@ -908,35 +908,141 @@ function displayStats(results) {
|
|||||||
function displayDifferences() {
|
function displayDifferences() {
|
||||||
const differencesContainer = document.getElementById('differences');
|
const differencesContainer = document.getElementById('differences');
|
||||||
|
|
||||||
if (!filteredDifferenceResults.length) {
|
// 保存当前搜索框的值
|
||||||
differencesContainer.innerHTML = '<p class="text-success"><i class="fas fa-check"></i> 未发现差异</p>';
|
const currentSearchValue = document.getElementById('differenceSearch')?.value || '';
|
||||||
|
|
||||||
|
if (!filteredDifferenceResults || !filteredDifferenceResults.length) {
|
||||||
|
// 显示搜索和控制界面,即使没有结果
|
||||||
|
let html = `
|
||||||
|
<!-- 字段筛选按钮和操作按钮 -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="btn-group btn-group-sm flex-wrap" role="group">
|
||||||
|
<button type="button" class="btn btn-outline-primary active" onclick="filterByField('')">
|
||||||
|
全部 (0)
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="d-flex justify-content-end">
|
||||||
|
<button type="button" class="btn btn-outline-primary btn-sm me-2" onclick="toggleAllDifferenceCollapse()">
|
||||||
|
<i class="fas fa-expand-alt"></i> <span id="toggleAllText">全部收起</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-success btn-sm" onclick="copyDifferenceKeys()">
|
||||||
|
<i class="fas fa-copy"></i> 复制差异主键
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 搜索框 -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text"><i class="fas fa-search"></i></span>
|
||||||
|
<input type="text" class="form-control" placeholder="搜索主键、字段名或值内容..."
|
||||||
|
onkeypress="if(event.key === 'Enter') searchDifferenceResults(this.value)"
|
||||||
|
id="differenceSearch"
|
||||||
|
value="${currentSearchValue}">
|
||||||
|
<button class="btn btn-outline-secondary" type="button" onclick="searchDifferenceResults(document.getElementById('differenceSearch').value)">
|
||||||
|
<i class="fas fa-search"></i> 搜索
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-secondary" type="button" onclick="clearDifferenceSearch()">
|
||||||
|
<i class="fas fa-times"></i> 清除
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 无结果提示 -->
|
||||||
|
<div class="alert alert-info text-center">
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
${currentSearchValue ? `没有找到包含"${escapeHtml(currentSearchValue)}"的差异记录` : '未发现差异'}
|
||||||
|
${currentSearchValue ? '<br><small class="text-muted">请尝试其他搜索条件或点击"清除"按钮查看所有结果</small>' : ''}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
differencesContainer.innerHTML = html;
|
||||||
|
|
||||||
|
// 恢复搜索框的焦点和光标位置
|
||||||
|
if (currentSearchValue) {
|
||||||
|
const searchInput = document.getElementById('differenceSearch');
|
||||||
|
if (searchInput) {
|
||||||
|
searchInput.focus();
|
||||||
|
searchInput.setSelectionRange(searchInput.value.length, searchInput.value.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算分页
|
// 调试日志:检查差异数据
|
||||||
const totalPages = Math.ceil(filteredDifferenceResults.length / differencePageSize);
|
console.log('差异数据:', filteredDifferenceResults);
|
||||||
|
|
||||||
|
// 按主键分组差异
|
||||||
|
const groupedDifferences = groupDifferencesByKey(filteredDifferenceResults);
|
||||||
|
const totalGroups = Object.keys(groupedDifferences).length;
|
||||||
|
|
||||||
|
// 计算分页(基于主键组)
|
||||||
|
const groupKeys = Object.keys(groupedDifferences);
|
||||||
|
const totalPages = Math.ceil(groupKeys.length / differencePageSize);
|
||||||
const startIndex = (currentDifferencePage - 1) * differencePageSize;
|
const startIndex = (currentDifferencePage - 1) * differencePageSize;
|
||||||
const endIndex = startIndex + differencePageSize;
|
const endIndex = startIndex + differencePageSize;
|
||||||
const currentPageData = filteredDifferenceResults.slice(startIndex, endIndex);
|
const currentPageKeys = groupKeys.slice(startIndex, endIndex);
|
||||||
|
|
||||||
|
// 统计字段差异类型
|
||||||
|
const fieldStats = {};
|
||||||
|
filteredDifferenceResults.forEach(diff => {
|
||||||
|
if (diff.field) {
|
||||||
|
fieldStats[diff.field] = (fieldStats[diff.field] || 0) + 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let html = `
|
let html = `
|
||||||
|
<!-- 字段筛选按钮和操作按钮 -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="btn-group btn-group-sm flex-wrap" role="group">
|
||||||
|
<button type="button" class="btn btn-outline-primary active" onclick="filterByField('')">
|
||||||
|
全部 (${filteredDifferenceResults.length})
|
||||||
|
</button>
|
||||||
|
${Object.entries(fieldStats).map(([field, count]) => `
|
||||||
|
<button type="button" class="btn btn-outline-secondary" onclick="filterByField('${field}')">
|
||||||
|
${field} (${count})
|
||||||
|
</button>
|
||||||
|
`).join('')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="d-flex justify-content-end">
|
||||||
|
<button type="button" class="btn btn-outline-primary btn-sm me-2" onclick="toggleAllDifferenceCollapse()">
|
||||||
|
<i class="fas fa-expand-alt"></i> <span id="toggleAllText">全部收起</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-success btn-sm" onclick="copyDifferenceKeys()">
|
||||||
|
<i class="fas fa-copy"></i> 复制差异主键
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 分页控制 -->
|
<!-- 分页控制 -->
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<label class="form-label me-2 mb-0">每页显示:</label>
|
<label class="form-label me-2 mb-0">每页显示:</label>
|
||||||
<select class="form-select form-select-sm me-2" style="width: auto;" onchange="changeDifferencePageSize(this.value)">
|
<select class="form-select form-select-sm me-2" style="width: auto;" onchange="changeDifferencePageSize(this.value)">
|
||||||
<option value="5" ${differencePageSize == 5 ? 'selected' : ''}>5条</option>
|
<option value="5" ${differencePageSize == 5 ? 'selected' : ''}>5组</option>
|
||||||
<option value="10" ${differencePageSize == 10 ? 'selected' : ''}>10条</option>
|
<option value="10" ${differencePageSize == 10 ? 'selected' : ''}>10组</option>
|
||||||
<option value="20" ${differencePageSize == 20 ? 'selected' : ''}>20条</option>
|
<option value="20" ${differencePageSize == 20 ? 'selected' : ''}>20组</option>
|
||||||
<option value="50" ${differencePageSize == 50 ? 'selected' : ''}>50条</option>
|
<option value="50" ${differencePageSize == 50 ? 'selected' : ''}>50组</option>
|
||||||
<option value="100" ${differencePageSize == 100 ? 'selected' : ''}>100条</option>
|
<option value="100" ${differencePageSize == 100 ? 'selected' : ''}>100组</option>
|
||||||
<option value="custom_diff">自定义</option>
|
<option value="custom_diff">自定义</option>
|
||||||
</select>
|
</select>
|
||||||
<input type="number" class="form-control form-control-sm me-2" style="width: 80px; display: none;"
|
<input type="number" class="form-control form-control-sm me-2" style="width: 80px; display: none;"
|
||||||
id="customDiffPageSize" placeholder="数量" min="1" max="1000"
|
id="customDiffPageSize" placeholder="数量" min="1" max="1000"
|
||||||
onchange="setCustomDifferencePageSize(this.value)" onkeypress="handleCustomDiffPageSizeEnter(event)">
|
onchange="setCustomDifferencePageSize(this.value)" onkeypress="handleCustomDiffPageSizeEnter(event)">
|
||||||
<span class="ms-2 text-muted">共 ${filteredDifferenceResults.length} 条差异记录</span>
|
<span class="ms-2 text-muted">共 ${totalGroups} 个主键组,${filteredDifferenceResults.length} 条差异记录</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
@@ -951,94 +1057,124 @@ function displayDifferences() {
|
|||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<span class="input-group-text"><i class="fas fa-search"></i></span>
|
<span class="input-group-text"><i class="fas fa-search"></i></span>
|
||||||
<input type="text" class="form-control" placeholder="搜索主键、字段名或值内容..."
|
<input type="text" class="form-control" placeholder="搜索主键、字段名或值内容..."
|
||||||
onkeyup="searchDifferenceResults(this.value)" id="differenceSearch">
|
onkeypress="if(event.key === 'Enter') searchDifferenceResults(this.value)"
|
||||||
|
id="differenceSearch"
|
||||||
|
value="${currentSearchValue}">
|
||||||
|
<button class="btn btn-outline-secondary" type="button" onclick="searchDifferenceResults(document.getElementById('differenceSearch').value)">
|
||||||
|
<i class="fas fa-search"></i> 搜索
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-secondary" type="button" onclick="clearDifferenceSearch()">
|
||||||
|
<i class="fas fa-times"></i> 清除
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// 显示当前页数据
|
// 显示当前页的主键组
|
||||||
currentPageData.forEach((diff, index) => {
|
currentPageKeys.forEach((keyStr, groupIndex) => {
|
||||||
const globalIndex = startIndex + index + 1;
|
const diffs = groupedDifferences[keyStr];
|
||||||
if (diff.message) {
|
const globalIndex = startIndex + groupIndex + 1;
|
||||||
// 记录不存在的情况
|
|
||||||
html += `
|
// 将字符串化的key解析回对象
|
||||||
<div class="difference-item card mb-3 border-warning">
|
let keyObj;
|
||||||
<div class="card-header bg-light">
|
try {
|
||||||
<div class="row align-items-center">
|
keyObj = JSON.parse(keyStr);
|
||||||
<div class="col">
|
} catch (e) {
|
||||||
<strong>差异 #${globalIndex}</strong>
|
// 如果解析失败,假设它本身就是一个简单值
|
||||||
<span class="badge bg-warning ms-2">记录缺失</span>
|
keyObj = keyStr;
|
||||||
</div>
|
}
|
||||||
<div class="col-auto">
|
|
||||||
<button class="btn btn-sm btn-outline-info" onclick='showDifferenceRawData(${JSON.stringify(JSON.stringify(diff.key))})'>
|
html += `
|
||||||
<i class="fas fa-code"></i> 原生数据
|
<div class="card mb-3 border-primary">
|
||||||
</button>
|
<div class="card-header bg-primary bg-opacity-10">
|
||||||
</div>
|
<div class="row align-items-center">
|
||||||
|
<div class="col">
|
||||||
|
<strong>主键组 #${globalIndex}</strong>
|
||||||
|
<span class="badge bg-primary ms-2">${diffs.length} 个差异字段</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<button class="btn btn-sm btn-outline-info" onclick='showDifferenceRawData(${JSON.stringify(JSON.stringify(keyObj))})'>
|
||||||
|
<i class="fas fa-code"></i> 原生数据
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-0 mt-2"><strong>主键:</strong> ${formatCompositeKey(diff.key)}</p>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<p class="text-warning"><i class="fas fa-exclamation-triangle"></i> ${diff.message}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<p class="mb-0 mt-2"><strong>主键:</strong> ${formatCompositeKey(keyObj)}</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
<div class="collapse show" id="keyGroup${globalIndex}">
|
||||||
} else {
|
<div class="card-body">
|
||||||
// 字段值差异的情况
|
`;
|
||||||
const isJson = diff.is_json;
|
|
||||||
const isArray = diff.is_array;
|
// 显示该主键下的所有差异字段
|
||||||
const jsonClass = isJson ? 'json-field' : '';
|
diffs.forEach((diff, diffIndex) => {
|
||||||
|
if (diff.message) {
|
||||||
html += `
|
// 记录不存在的情况
|
||||||
<div class="difference-item card mb-3 border-danger">
|
html += `
|
||||||
<div class="card-header bg-light">
|
<div class="alert alert-warning mb-2">
|
||||||
<div class="row align-items-center">
|
<i class="fas fa-exclamation-triangle"></i> ${diff.message}
|
||||||
<div class="col">
|
</div>
|
||||||
<strong>差异 #${globalIndex}</strong>
|
`;
|
||||||
<span class="badge bg-danger ms-2">字段差异</span>
|
} else {
|
||||||
${isJson ? '<span class="badge bg-info ms-2">JSON字段</span>' : ''}
|
// 字段值差异的情况
|
||||||
${isArray ? '<span class="badge bg-warning ms-2">数组字段</span>' : ''}
|
const isJson = diff.is_json;
|
||||||
</div>
|
const isArray = diff.is_array;
|
||||||
<div class="col-auto">
|
const jsonClass = isJson ? 'json-field' : '';
|
||||||
<button class="btn btn-sm btn-outline-secondary me-2" type="button"
|
const fieldId = `field_${globalIndex}_${diffIndex}`;
|
||||||
data-bs-toggle="collapse" data-bs-target="#diffCollapse${globalIndex}">
|
|
||||||
<i class="fas fa-eye"></i> 查看详情
|
// 调试:打印差异数据
|
||||||
</button>
|
console.log(`差异 ${diff.field}:`, {
|
||||||
<button class="btn btn-sm btn-outline-info" onclick='showDifferenceRawData(${JSON.stringify(JSON.stringify(diff.key))})'>
|
pro_value: diff.pro_value,
|
||||||
<i class="fas fa-code"></i> 原生数据
|
test_value: diff.test_value,
|
||||||
</button>
|
is_json: diff.is_json,
|
||||||
|
is_array: diff.is_array
|
||||||
|
});
|
||||||
|
|
||||||
|
// 确保值存在
|
||||||
|
const proValue = diff.pro_value !== undefined && diff.pro_value !== null ? diff.pro_value : '无数据';
|
||||||
|
const testValue = diff.test_value !== undefined && diff.test_value !== null ? diff.test_value : '无数据';
|
||||||
|
|
||||||
|
html += `
|
||||||
|
<div class="mb-3 border-start border-3 border-danger ps-3">
|
||||||
|
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||||
|
<div>
|
||||||
|
<strong class="text-danger">${diff.field}</strong>
|
||||||
|
${isJson ? '<span class="badge bg-info ms-2">JSON</span>' : ''}
|
||||||
|
${isArray ? '<span class="badge bg-warning ms-2">数组</span>' : ''}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-0 mt-2"><strong>主键:</strong> ${formatCompositeKey(diff.key)}</p>
|
<div class="collapse show" id="${fieldId}">
|
||||||
<p class="mb-0"><strong>差异字段:</strong> ${diff.field}</p>
|
<div class="row">
|
||||||
</div>
|
<div class="col-md-6">
|
||||||
<div class="collapse" id="diffCollapse${globalIndex}">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-header bg-success bg-opacity-10">
|
||||||
<div class="mb-4">
|
<small class="text-success fw-bold">生产环境</small>
|
||||||
<div class="field-header mb-2">
|
</div>
|
||||||
<i class="fas fa-tag text-primary"></i>
|
<div class="card-body p-2">
|
||||||
<strong>${diff.field}</strong>
|
<pre class="field-value mb-0 ${jsonClass}" style="max-height: 200px; overflow-y: auto;">${escapeHtml(proValue)}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
<!-- 环境对比数据行 -->
|
<div class="card">
|
||||||
<div class="row">
|
<div class="card-header bg-info bg-opacity-10">
|
||||||
<div class="col-12">
|
<small class="text-info fw-bold">测试环境</small>
|
||||||
<div class="field-container position-relative">
|
</div>
|
||||||
<pre class="field-value bg-light p-2 rounded mb-0 ${jsonClass}" style="max-height: 400px; overflow-y: auto; margin: 0;">${escapeHtml(diff.pro_value)}
|
<div class="card-body p-2">
|
||||||
${escapeHtml(diff.test_value)}</pre>
|
<pre class="field-value mb-0 ${jsonClass}" style="max-height: 200px; overflow-y: auto;">${escapeHtml(testValue)}</pre>
|
||||||
<button class="btn btn-sm btn-outline-secondary copy-btn"
|
|
||||||
onclick="copyToClipboard('${escapeForJs(diff.pro_value)}\n${escapeForJs(diff.test_value)}', this)"
|
|
||||||
title="复制全部内容">
|
|
||||||
<i class="fas fa-copy"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
html += `
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
</div>
|
||||||
}
|
`;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 底部分页
|
// 底部分页
|
||||||
@@ -1055,12 +1191,41 @@ ${escapeHtml(diff.test_value)}</pre>
|
|||||||
}
|
}
|
||||||
|
|
||||||
differencesContainer.innerHTML = html;
|
differencesContainer.innerHTML = html;
|
||||||
|
|
||||||
|
// 初始化全部展开/收起按钮的状态
|
||||||
|
const toggleButton = document.getElementById('toggleAllText');
|
||||||
|
if (toggleButton) {
|
||||||
|
// 由于默认展开,按钮文本应该是"全部收起"
|
||||||
|
toggleButton.innerHTML = '全部收起';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 恢复搜索框的焦点和光标位置
|
||||||
|
if (currentSearchValue) {
|
||||||
|
const searchInput = document.getElementById('differenceSearch');
|
||||||
|
if (searchInput) {
|
||||||
|
searchInput.focus();
|
||||||
|
searchInput.setSelectionRange(searchInput.value.length, searchInput.value.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTML转义函数,防止XSS
|
// HTML转义函数,防止XSS
|
||||||
function escapeHtml(text) {
|
function escapeHtml(text) {
|
||||||
|
// 处理undefined和null值
|
||||||
|
if (text === undefined || text === null) {
|
||||||
|
return '<span class="text-muted">无数据</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保是字符串
|
||||||
|
const str = String(text);
|
||||||
|
|
||||||
|
// 如果是空字符串
|
||||||
|
if (str === '') {
|
||||||
|
return '<span class="text-muted">空值</span>';
|
||||||
|
}
|
||||||
|
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.textContent = text;
|
div.textContent = str;
|
||||||
return div.innerHTML;
|
return div.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1068,8 +1233,73 @@ function escapeHtml(text) {
|
|||||||
function displayIdenticalResults() {
|
function displayIdenticalResults() {
|
||||||
const identicalContainer = document.getElementById('identical-results');
|
const identicalContainer = document.getElementById('identical-results');
|
||||||
|
|
||||||
|
// 保存当前搜索框的值
|
||||||
|
const currentSearchValue = document.getElementById('identicalSearch')?.value || '';
|
||||||
|
|
||||||
if (!filteredIdenticalResults.length) {
|
if (!filteredIdenticalResults.length) {
|
||||||
identicalContainer.innerHTML = '<p class="text-muted"><i class="fas fa-info-circle"></i> 没有完全相同的记录</p>';
|
// 显示搜索和控制界面,即使没有结果
|
||||||
|
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="changePageSize(this.value)">
|
||||||
|
<option value="5" ${identicalPageSize == 10 ? 'selected' : ''}>10条</option>
|
||||||
|
<option value="10" ${identicalPageSize == 50 ? 'selected' : ''}>50条</option>
|
||||||
|
<option value="20" ${identicalPageSize == 100 ? 'selected' : ''}>100条</option>
|
||||||
|
<option value="50" ${identicalPageSize == 200 ? 'selected' : ''}>200条</option>
|
||||||
|
<option value="100" ${identicalPageSize == 500 ? 'selected' : ''}>500条</option>
|
||||||
|
<option value="custom">自定义</option>
|
||||||
|
</select>
|
||||||
|
<input type="number" class="form-control form-control-sm me-2" style="width: 80px; display: none;"
|
||||||
|
id="customPageSize" placeholder="数量" min="1" max="1000"
|
||||||
|
onchange="setCustomPageSize(this.value)" onkeypress="handleCustomPageSizeEnter(event)">
|
||||||
|
<span class="ms-2 text-muted">共 0 条记录</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="d-flex justify-content-end align-items-center">
|
||||||
|
<!-- 无分页控制 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 搜索框 -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text"><i class="fas fa-search"></i></span>
|
||||||
|
<input type="text" class="form-control" placeholder="搜索主键或字段内容..."
|
||||||
|
onkeypress="if(event.key === 'Enter') searchIdenticalResults(this.value)"
|
||||||
|
id="identicalSearch"
|
||||||
|
value="${currentIdenticalSearchTerm || ''}">
|
||||||
|
<button class="btn btn-outline-secondary" type="button" onclick="searchIdenticalResults(document.getElementById('identicalSearch').value)">
|
||||||
|
<i class="fas fa-search"></i> 搜索
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-secondary" type="button" onclick="clearIdenticalSearch()">
|
||||||
|
<i class="fas fa-times"></i> 清除
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 无结果提示 -->
|
||||||
|
<div class="alert alert-info text-center">
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
${(currentIdenticalSearchTerm || currentSearchValue) ? `没有找到包含"${escapeHtml(currentIdenticalSearchTerm || currentSearchValue)}"的相同记录` : '没有完全相同的记录'}
|
||||||
|
${(currentIdenticalSearchTerm || currentSearchValue) ? '<br><small class="text-muted">请尝试其他搜索条件或点击"清除"按钮查看所有结果</small>' : ''}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
identicalContainer.innerHTML = html;
|
||||||
|
|
||||||
|
// 恢复搜索框的焦点和光标位置
|
||||||
|
if (currentSearchValue) {
|
||||||
|
const searchInput = document.getElementById('identicalSearch');
|
||||||
|
if (searchInput) {
|
||||||
|
searchInput.focus();
|
||||||
|
searchInput.setSelectionRange(searchInput.value.length, searchInput.value.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1111,7 +1341,15 @@ function displayIdenticalResults() {
|
|||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<span class="input-group-text"><i class="fas fa-search"></i></span>
|
<span class="input-group-text"><i class="fas fa-search"></i></span>
|
||||||
<input type="text" class="form-control" placeholder="搜索主键或字段内容..."
|
<input type="text" class="form-control" placeholder="搜索主键或字段内容..."
|
||||||
onkeyup="searchIdenticalResults(this.value)" id="identicalSearch">
|
onkeypress="if(event.key === 'Enter') searchIdenticalResults(this.value)"
|
||||||
|
id="identicalSearch"
|
||||||
|
value="${currentIdenticalSearchTerm || ''}">
|
||||||
|
<button class="btn btn-outline-secondary" type="button" onclick="searchIdenticalResults(document.getElementById('identicalSearch').value)">
|
||||||
|
<i class="fas fa-search"></i> 搜索
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-secondary" type="button" onclick="clearIdenticalSearch()">
|
||||||
|
<i class="fas fa-times"></i> 清除
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -1128,10 +1366,6 @@ function displayIdenticalResults() {
|
|||||||
<span class="badge bg-success ms-2">完全匹配</span>
|
<span class="badge bg-success ms-2">完全匹配</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<button class="btn btn-sm btn-outline-secondary me-2" type="button"
|
|
||||||
data-bs-toggle="collapse" data-bs-target="#collapse${globalIndex}">
|
|
||||||
<i class="fas fa-eye"></i> 查看详情
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-sm btn-outline-info" onclick='showRawData(${JSON.stringify(JSON.stringify(result.key))})'>
|
<button class="btn btn-sm btn-outline-info" onclick='showRawData(${JSON.stringify(JSON.stringify(result.key))})'>
|
||||||
<i class="fas fa-code"></i> 原生数据
|
<i class="fas fa-code"></i> 原生数据
|
||||||
</button>
|
</button>
|
||||||
@@ -1139,7 +1373,7 @@ function displayIdenticalResults() {
|
|||||||
</div>
|
</div>
|
||||||
<p class="mb-0 mt-2"><strong>主键:</strong> ${formatCompositeKey(result.key)}</p>
|
<p class="mb-0 mt-2"><strong>主键:</strong> ${formatCompositeKey(result.key)}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="collapse" id="collapse${globalIndex}">
|
<div class="collapse show" id="collapse${globalIndex}">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -1219,6 +1453,15 @@ ${escapeHtml(String(testValue))}</pre>
|
|||||||
}
|
}
|
||||||
|
|
||||||
identicalContainer.innerHTML = html;
|
identicalContainer.innerHTML = html;
|
||||||
|
|
||||||
|
// 恢复搜索框的焦点和光标位置
|
||||||
|
if (currentSearchValue) {
|
||||||
|
const searchInput = document.getElementById('identicalSearch');
|
||||||
|
if (searchInput) {
|
||||||
|
searchInput.focus();
|
||||||
|
searchInput.setSelectionRange(searchInput.value.length, searchInput.value.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成分页导航
|
// 生成分页导航
|
||||||
@@ -1334,9 +1577,14 @@ function handleCustomPageSizeEnter(event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 搜索相同结果
|
// 搜索相同结果
|
||||||
|
let currentIdenticalSearchTerm = '';
|
||||||
|
|
||||||
function searchIdenticalResults(searchTerm) {
|
function searchIdenticalResults(searchTerm) {
|
||||||
if (!currentResults) return;
|
if (!currentResults) return;
|
||||||
|
|
||||||
|
// 保存搜索词
|
||||||
|
currentIdenticalSearchTerm = searchTerm;
|
||||||
|
|
||||||
if (!searchTerm.trim()) {
|
if (!searchTerm.trim()) {
|
||||||
filteredIdenticalResults = currentResults.identical_results;
|
filteredIdenticalResults = currentResults.identical_results;
|
||||||
} else {
|
} else {
|
||||||
@@ -1361,6 +1609,15 @@ function searchIdenticalResults(searchTerm) {
|
|||||||
displayIdenticalResults();
|
displayIdenticalResults();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清除相同记录搜索
|
||||||
|
function clearIdenticalSearch() {
|
||||||
|
currentIdenticalSearchTerm = '';
|
||||||
|
document.getElementById('identicalSearch').value = '';
|
||||||
|
filteredIdenticalResults = currentResults.identical_results;
|
||||||
|
currentIdenticalPage = 1;
|
||||||
|
displayIdenticalResults();
|
||||||
|
}
|
||||||
|
|
||||||
// 生成差异分页导航
|
// 生成差异分页导航
|
||||||
function generateDifferencePagination(currentPage, totalPages) {
|
function generateDifferencePagination(currentPage, totalPages) {
|
||||||
if (totalPages <= 1) return '';
|
if (totalPages <= 1) return '';
|
||||||
@@ -1474,14 +1731,28 @@ function handleCustomDiffPageSizeEnter(event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 搜索差异结果
|
// 搜索差异结果
|
||||||
|
let currentSearchTerm = '';
|
||||||
|
|
||||||
function searchDifferenceResults(searchTerm) {
|
function searchDifferenceResults(searchTerm) {
|
||||||
if (!currentResults) return;
|
if (!currentResults) return;
|
||||||
|
|
||||||
|
// 保存搜索词
|
||||||
|
currentSearchTerm = searchTerm;
|
||||||
|
|
||||||
if (!searchTerm.trim()) {
|
if (!searchTerm.trim()) {
|
||||||
filteredDifferenceResults = currentResults.differences;
|
// 如果有字段筛选,应用字段筛选;否则显示全部
|
||||||
|
if (currentFieldFilter) {
|
||||||
|
filteredDifferenceResults = currentResults.differences.filter(diff => diff.field === currentFieldFilter);
|
||||||
|
} else {
|
||||||
|
filteredDifferenceResults = currentResults.differences;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const term = searchTerm.toLowerCase();
|
const term = searchTerm.toLowerCase();
|
||||||
filteredDifferenceResults = currentResults.differences.filter(diff => {
|
let baseResults = currentFieldFilter ?
|
||||||
|
currentResults.differences.filter(diff => diff.field === currentFieldFilter) :
|
||||||
|
currentResults.differences;
|
||||||
|
|
||||||
|
filteredDifferenceResults = baseResults.filter(diff => {
|
||||||
// 搜索主键
|
// 搜索主键
|
||||||
const keyStr = JSON.stringify(diff.key).toLowerCase();
|
const keyStr = JSON.stringify(diff.key).toLowerCase();
|
||||||
if (keyStr.includes(term)) return true;
|
if (keyStr.includes(term)) return true;
|
||||||
@@ -1504,6 +1775,22 @@ function searchDifferenceResults(searchTerm) {
|
|||||||
displayDifferences();
|
displayDifferences();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清除差异搜索
|
||||||
|
function clearDifferenceSearch() {
|
||||||
|
currentSearchTerm = '';
|
||||||
|
document.getElementById('differenceSearch').value = '';
|
||||||
|
|
||||||
|
// 重新应用字段筛选
|
||||||
|
if (currentFieldFilter) {
|
||||||
|
filteredDifferenceResults = currentResults.differences.filter(diff => diff.field === currentFieldFilter);
|
||||||
|
} else {
|
||||||
|
filteredDifferenceResults = currentResults.differences;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentDifferencePage = 1;
|
||||||
|
displayDifferences();
|
||||||
|
}
|
||||||
|
|
||||||
// 复制到剪贴板
|
// 复制到剪贴板
|
||||||
function copyToClipboard(text, button) {
|
function copyToClipboard(text, button) {
|
||||||
navigator.clipboard.writeText(text).then(function() {
|
navigator.clipboard.writeText(text).then(function() {
|
||||||
@@ -1551,6 +1838,7 @@ function showRawData(keyStr) {
|
|||||||
try {
|
try {
|
||||||
// 解析key
|
// 解析key
|
||||||
const key = JSON.parse(keyStr);
|
const key = JSON.parse(keyStr);
|
||||||
|
console.log('查找原始数据,key:', key);
|
||||||
|
|
||||||
// 在原生数据中查找对应的记录
|
// 在原生数据中查找对应的记录
|
||||||
let proData = null;
|
let proData = null;
|
||||||
@@ -1562,15 +1850,40 @@ function showRawData(keyStr) {
|
|||||||
// 支持复合主键比较
|
// 支持复合主键比较
|
||||||
if (typeof key === 'object' && !Array.isArray(key)) {
|
if (typeof key === 'object' && !Array.isArray(key)) {
|
||||||
// 复合主键情况:比较所有主键字段
|
// 复合主键情况:比较所有主键字段
|
||||||
return Object.keys(key).every(keyField =>
|
const matches = Object.keys(key).every(keyField => {
|
||||||
JSON.stringify(item[keyField]) === JSON.stringify(key[keyField])
|
const itemValue = item[keyField];
|
||||||
);
|
const keyValue = key[keyField];
|
||||||
|
|
||||||
|
// 如果都是undefined或null,认为匹配
|
||||||
|
if (itemValue == null && keyValue == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为字符串进行比较
|
||||||
|
const itemStr = String(itemValue);
|
||||||
|
const keyStr = String(keyValue);
|
||||||
|
|
||||||
|
// 直接比较字符串
|
||||||
|
return itemStr === keyStr;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (matches) {
|
||||||
|
console.log('找到生产环境数据:', item);
|
||||||
|
}
|
||||||
|
return matches;
|
||||||
} else {
|
} else {
|
||||||
// 单主键情况:保持原有逻辑
|
// 单主键情况(兼容旧代码)
|
||||||
const keyField = Object.keys(key)[0];
|
return false;
|
||||||
return JSON.stringify(item[keyField]) === JSON.stringify(key[keyField]);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 如果没找到,输出调试信息
|
||||||
|
if (!proData) {
|
||||||
|
console.log('未找到生产数据,查找的key:', key);
|
||||||
|
console.log('可用的数据:', currentResults.raw_pro_data.map(item => ({
|
||||||
|
statusid: item.statusid
|
||||||
|
})));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查找测试环境数据
|
// 查找测试环境数据
|
||||||
@@ -1579,17 +1892,44 @@ function showRawData(keyStr) {
|
|||||||
// 支持复合主键比较
|
// 支持复合主键比较
|
||||||
if (typeof key === 'object' && !Array.isArray(key)) {
|
if (typeof key === 'object' && !Array.isArray(key)) {
|
||||||
// 复合主键情况:比较所有主键字段
|
// 复合主键情况:比较所有主键字段
|
||||||
return Object.keys(key).every(keyField =>
|
const matches = Object.keys(key).every(keyField => {
|
||||||
JSON.stringify(item[keyField]) === JSON.stringify(key[keyField])
|
const itemValue = item[keyField];
|
||||||
);
|
const keyValue = key[keyField];
|
||||||
|
|
||||||
|
// 如果都是undefined或null,认为匹配
|
||||||
|
if (itemValue == null && keyValue == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为字符串进行比较
|
||||||
|
const itemStr = String(itemValue);
|
||||||
|
const keyStr = String(keyValue);
|
||||||
|
|
||||||
|
// 直接比较字符串
|
||||||
|
return itemStr === keyStr;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (matches) {
|
||||||
|
console.log('找到测试环境数据:', item);
|
||||||
|
}
|
||||||
|
return matches;
|
||||||
} else {
|
} else {
|
||||||
// 单主键情况:保持原有逻辑
|
// 单主键情况(兼容旧代码)
|
||||||
const keyField = Object.keys(key)[0];
|
return false;
|
||||||
return JSON.stringify(item[keyField]) === JSON.stringify(key[keyField]);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 如果没找到,输出调试信息
|
||||||
|
if (!testData) {
|
||||||
|
console.log('未找到测试数据,查找的key:', key);
|
||||||
|
console.log('可用的数据:', currentResults.raw_test_data.map(item => ({
|
||||||
|
statusid: item.statusid
|
||||||
|
})));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('查找结果 - 生产数据:', proData, '测试数据:', testData);
|
||||||
|
|
||||||
// 创建模态框内容
|
// 创建模态框内容
|
||||||
const modalContent = `
|
const modalContent = `
|
||||||
<div class="modal fade" id="rawDataModal" tabindex="-1">
|
<div class="modal fade" id="rawDataModal" tabindex="-1">
|
||||||
@@ -1713,14 +2053,47 @@ function showRawData(keyStr) {
|
|||||||
|
|
||||||
// 延迟渲染对比视图和树形视图
|
// 延迟渲染对比视图和树形视图
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
renderDiffView(proData, testData);
|
console.log('开始渲染视图,生产数据:', proData, '测试数据:', testData);
|
||||||
renderTreeView('proTreeView', proData);
|
|
||||||
renderTreeView('testTreeView', testData);
|
// 渲染对比视图
|
||||||
|
try {
|
||||||
|
renderDiffView(proData, testData);
|
||||||
|
console.log('对比视图渲染完成');
|
||||||
|
} catch (e) {
|
||||||
|
console.error('对比视图渲染失败:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染树形视图
|
||||||
|
try {
|
||||||
|
renderTreeView('proTreeView', proData);
|
||||||
|
renderTreeView('testTreeView', testData);
|
||||||
|
console.log('树形视图渲染完成');
|
||||||
|
} catch (e) {
|
||||||
|
console.error('树形视图渲染失败:', e);
|
||||||
|
}
|
||||||
|
|
||||||
// 为格式化视图添加同步滚动
|
// 为格式化视图添加同步滚动
|
||||||
setupSyncScroll('formatted');
|
setupSyncScroll('formatted');
|
||||||
// 为树形视图添加同步滚动
|
// 为树形视图添加同步滚动
|
||||||
setupSyncScroll('tree');
|
setupSyncScroll('tree');
|
||||||
|
|
||||||
|
// 添加标签页切换事件监听器,确保切换时重新渲染
|
||||||
|
const tabButtons = document.querySelectorAll('#rawDataTabs button[data-bs-toggle="tab"]');
|
||||||
|
tabButtons.forEach(button => {
|
||||||
|
button.addEventListener('shown.bs.tab', (event) => {
|
||||||
|
const targetId = event.target.getAttribute('data-bs-target');
|
||||||
|
console.log('切换到标签页:', targetId);
|
||||||
|
|
||||||
|
if (targetId === '#diff') {
|
||||||
|
// 重新渲染对比视图
|
||||||
|
renderDiffView(proData, testData);
|
||||||
|
} else if (targetId === '#tree') {
|
||||||
|
// 重新渲染树形视图
|
||||||
|
renderTreeView('proTreeView', proData);
|
||||||
|
renderTreeView('testTreeView', testData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1746,6 +2119,101 @@ function copyRawData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 复制差异主键
|
||||||
|
function copyDifferenceKeys() {
|
||||||
|
if (!filteredDifferenceResults || filteredDifferenceResults.length === 0) {
|
||||||
|
showAlert('warning', '无差异数据可复制');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 收集所有差异记录的主键
|
||||||
|
const differenceKeys = [];
|
||||||
|
const uniqueKeys = new Set();
|
||||||
|
|
||||||
|
filteredDifferenceResults.forEach(diff => {
|
||||||
|
if (diff.key) {
|
||||||
|
let keyText = '';
|
||||||
|
|
||||||
|
if (typeof diff.key === 'object' && !Array.isArray(diff.key)) {
|
||||||
|
// 复合主键:转换为逗号分隔格式
|
||||||
|
keyText = Object.values(diff.key).join(',');
|
||||||
|
} else {
|
||||||
|
// 单主键或其他格式
|
||||||
|
keyText = String(diff.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 避免重复主键
|
||||||
|
if (!uniqueKeys.has(keyText)) {
|
||||||
|
uniqueKeys.add(keyText);
|
||||||
|
differenceKeys.push(keyText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (differenceKeys.length === 0) {
|
||||||
|
showAlert('warning', '未找到有效的主键数据');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将主键列表转换为文本格式(每行一个)
|
||||||
|
const keyText = differenceKeys.join('\n');
|
||||||
|
|
||||||
|
// 复制到剪贴板
|
||||||
|
navigator.clipboard.writeText(keyText).then(() => {
|
||||||
|
showAlert('success', `已复制 ${differenceKeys.length} 个差异主键到剪贴板`);
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('复制失败:', err);
|
||||||
|
showAlert('danger', '复制失败,请手动选择复制');
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('处理差异主键失败:', error);
|
||||||
|
showAlert('danger', '处理差异主键失败: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换所有差异详情的展开/收起状态
|
||||||
|
function toggleAllDifferenceCollapse() {
|
||||||
|
// 获取当前页面的所有差异展开区域
|
||||||
|
const collapseElements = document.querySelectorAll('#differences [id^="keyGroup"]');
|
||||||
|
const toggleButton = document.getElementById('toggleAllText');
|
||||||
|
|
||||||
|
if (collapseElements.length === 0) {
|
||||||
|
showAlert('warning', '没有找到差异详情');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查当前状态 - 如果大部分是展开的,就全部收起;否则全部展开
|
||||||
|
let expandedCount = 0;
|
||||||
|
collapseElements.forEach(element => {
|
||||||
|
if (element.classList.contains('show')) {
|
||||||
|
expandedCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const shouldExpand = expandedCount < collapseElements.length / 2;
|
||||||
|
|
||||||
|
collapseElements.forEach(element => {
|
||||||
|
if (shouldExpand) {
|
||||||
|
// 展开
|
||||||
|
element.classList.add('show');
|
||||||
|
} else {
|
||||||
|
// 收起
|
||||||
|
element.classList.remove('show');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新按钮文本
|
||||||
|
if (shouldExpand) {
|
||||||
|
toggleButton.innerHTML = '全部收起';
|
||||||
|
showAlert('success', `已展开 ${collapseElements.length} 个差异详情`);
|
||||||
|
} else {
|
||||||
|
toggleButton.innerHTML = '全部展开';
|
||||||
|
showAlert('success', `已收起 ${collapseElements.length} 个差异详情`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 显示差异数据的原生数据
|
// 显示差异数据的原生数据
|
||||||
function showDifferenceRawData(keyStr) {
|
function showDifferenceRawData(keyStr) {
|
||||||
showRawData(keyStr); // 复用相同的原生数据显示逻辑
|
showRawData(keyStr); // 复用相同的原生数据显示逻辑
|
||||||
@@ -1755,6 +2223,12 @@ function showDifferenceRawData(keyStr) {
|
|||||||
function renderDiffView(proData, testData) {
|
function renderDiffView(proData, testData) {
|
||||||
const diffViewContainer = document.getElementById('diffView');
|
const diffViewContainer = document.getElementById('diffView');
|
||||||
|
|
||||||
|
// 确保容器存在
|
||||||
|
if (!diffViewContainer) {
|
||||||
|
console.error('对比视图容器不存在');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!proData && !testData) {
|
if (!proData && !testData) {
|
||||||
diffViewContainer.innerHTML = '<p class="text-muted text-center">无数据可对比</p>';
|
diffViewContainer.innerHTML = '<p class="text-muted text-center">无数据可对比</p>';
|
||||||
return;
|
return;
|
||||||
@@ -2136,6 +2610,12 @@ function normalizeValue(value) {
|
|||||||
function renderTreeView(containerId, data) {
|
function renderTreeView(containerId, data) {
|
||||||
const container = document.getElementById(containerId);
|
const container = document.getElementById(containerId);
|
||||||
|
|
||||||
|
// 确保容器存在
|
||||||
|
if (!container) {
|
||||||
|
console.error('树形视图容器不存在:', containerId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
container.innerHTML = '<p class="text-muted">无数据</p>';
|
container.innerHTML = '<p class="text-muted">无数据</p>';
|
||||||
return;
|
return;
|
||||||
@@ -3101,14 +3581,14 @@ function showImportDialog(env) {
|
|||||||
<p class="text-muted mb-3">请粘贴配置数据,支持以下格式:</p>
|
<p class="text-muted mb-3">请粘贴配置数据,支持以下格式:</p>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<small class="text-muted">示例格式:</small>
|
<small class="text-muted">示例格式:</small>
|
||||||
<pre class="bg-light p-2 rounded small">clusterName: "Hot Cluster"
|
<pre class="bg-light p-2 rounded small">clusterName: "Example Cluster"
|
||||||
clusterNodes: "10.20.2.22,10.20.2.23"
|
clusterNodes: "127.0.0.1,127.0.0.2"
|
||||||
port: 9042
|
port: 9042
|
||||||
datacenter: "cs01"
|
datacenter: "dc1"
|
||||||
username: "cbase"
|
username: "cassandra"
|
||||||
password: "antducbaseadmin@2022"
|
password: "example_password"
|
||||||
keyspace: "yuqing_skinny"
|
keyspace: "example_keyspace"
|
||||||
table: "status_test"</pre>
|
table: "example_table"</pre>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">配置数据:</label>
|
<label class="form-label">配置数据:</label>
|
||||||
@@ -3954,10 +4434,6 @@ function renderRawDataContent() {
|
|||||||
<span class="badge ${envBadgeClass} ms-2">${item.displayName}</span>
|
<span class="badge ${envBadgeClass} ms-2">${item.displayName}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<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))})'>
|
<button class="btn btn-sm btn-outline-info" onclick='copyRawRecord(${JSON.stringify(JSON.stringify(item.data))})'>
|
||||||
<i class="fas fa-copy"></i> 复制
|
<i class="fas fa-copy"></i> 复制
|
||||||
</button>
|
</button>
|
||||||
@@ -3965,7 +4441,7 @@ function renderRawDataContent() {
|
|||||||
</div>
|
</div>
|
||||||
<p class="mb-0 mt-2"><strong>主键:</strong> ${keyValue}</p>
|
<p class="mb-0 mt-2"><strong>主键:</strong> ${keyValue}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="collapse" id="rawCollapse${globalIndex}">
|
<div class="collapse show" id="rawCollapse${globalIndex}">
|
||||||
<div class="card-body">
|
<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>
|
<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>
|
||||||
@@ -4271,4 +4747,69 @@ async function batchDeleteQueryHistory() {
|
|||||||
console.error('批量删除Cassandra查询历史记录失败:', error);
|
console.error('批量删除Cassandra查询历史记录失败:', error);
|
||||||
showAlert('danger', `批量删除失败: ${error.message}`);
|
showAlert('danger', `批量删除失败: ${error.message}`);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按主键分组差异数据
|
||||||
|
function groupDifferencesByKey(differences) {
|
||||||
|
const grouped = {};
|
||||||
|
differences.forEach(diff => {
|
||||||
|
const keyStr = typeof diff.key === 'object' ? JSON.stringify(diff.key) : diff.key;
|
||||||
|
if (!grouped[keyStr]) {
|
||||||
|
grouped[keyStr] = [];
|
||||||
|
}
|
||||||
|
grouped[keyStr].push(diff);
|
||||||
|
});
|
||||||
|
return grouped;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按字段筛选差异
|
||||||
|
let currentFieldFilter = '';
|
||||||
|
|
||||||
|
function filterByField(field) {
|
||||||
|
currentFieldFilter = field;
|
||||||
|
|
||||||
|
// 更新按钮状态
|
||||||
|
const buttons = document.querySelectorAll('.btn-group button');
|
||||||
|
buttons.forEach(btn => {
|
||||||
|
btn.classList.remove('active', 'btn-outline-primary');
|
||||||
|
btn.classList.add('btn-outline-secondary');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 设置当前选中的按钮
|
||||||
|
if (field === '') {
|
||||||
|
buttons[0].classList.remove('btn-outline-secondary');
|
||||||
|
buttons[0].classList.add('btn-outline-primary', 'active');
|
||||||
|
} else {
|
||||||
|
buttons.forEach(btn => {
|
||||||
|
if (btn.textContent.includes(field + ' (')) {
|
||||||
|
btn.classList.remove('btn-outline-secondary');
|
||||||
|
btn.classList.add('btn-outline-primary', 'active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 筛选差异数据(考虑搜索条件)
|
||||||
|
let baseResults = field === '' ?
|
||||||
|
currentResults.differences :
|
||||||
|
currentResults.differences.filter(diff => diff.field === field);
|
||||||
|
|
||||||
|
// 如果有搜索条件,应用搜索过滤
|
||||||
|
if (currentSearchTerm && currentSearchTerm.trim()) {
|
||||||
|
const term = currentSearchTerm.toLowerCase();
|
||||||
|
filteredDifferenceResults = baseResults.filter(diff => {
|
||||||
|
const keyStr = JSON.stringify(diff.key).toLowerCase();
|
||||||
|
if (keyStr.includes(term)) return true;
|
||||||
|
if (diff.field && diff.field.toLowerCase().includes(term)) return true;
|
||||||
|
if (diff.pro_value && String(diff.pro_value).toLowerCase().includes(term)) return true;
|
||||||
|
if (diff.test_value && String(diff.test_value).toLowerCase().includes(term)) return true;
|
||||||
|
if (diff.message && diff.message.toLowerCase().includes(term)) return true;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
filteredDifferenceResults = baseResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置页码并重新显示
|
||||||
|
currentDifferencePage = 1;
|
||||||
|
displayDifferences();
|
||||||
}
|
}
|
@@ -1665,30 +1665,30 @@ function updateConfigTemplate() {
|
|||||||
if (format === 'yaml') {
|
if (format === 'yaml') {
|
||||||
templateElement.textContent = `# Redis集群配置示例 (YAML格式)
|
templateElement.textContent = `# Redis集群配置示例 (YAML格式)
|
||||||
cluster1:
|
cluster1:
|
||||||
clusterName: "redis-production"
|
clusterName: "redis-example1"
|
||||||
clusterAddress: "10.20.2.109:6470"
|
clusterAddress: "127.0.0.1:6379"
|
||||||
clusterPassword: ""
|
clusterPassword: ""
|
||||||
cachePrefix: "message.status.Reader."
|
cachePrefix: "message.status.Reader."
|
||||||
cacheTtl: 2000
|
cacheTtl: 2000
|
||||||
async: true
|
async: true
|
||||||
nodes:
|
nodes:
|
||||||
- host: "10.20.2.109"
|
- host: "127.0.0.1"
|
||||||
port: 6470
|
port: 6379
|
||||||
- host: "10.20.2.110"
|
- host: "127.0.0.2"
|
||||||
port: 6470
|
port: 6379
|
||||||
|
|
||||||
cluster2:
|
cluster2:
|
||||||
clusterName: "redis-test"
|
clusterName: "redis-example2"
|
||||||
clusterAddress: "10.20.2.109:6471"
|
clusterAddress: "127.0.0.1:6380"
|
||||||
clusterPassword: ""
|
clusterPassword: ""
|
||||||
cachePrefix: "message.status.Reader."
|
cachePrefix: "message.status.Reader."
|
||||||
cacheTtl: 2000
|
cacheTtl: 2000
|
||||||
async: true
|
async: true
|
||||||
nodes:
|
nodes:
|
||||||
- host: "10.20.2.109"
|
- host: "127.0.0.1"
|
||||||
port: 6471
|
port: 6380
|
||||||
- host: "10.20.2.110"
|
- host: "127.0.0.2"
|
||||||
port: 6471
|
port: 6380
|
||||||
|
|
||||||
queryOptions:
|
queryOptions:
|
||||||
mode: "random" # random 或 specified
|
mode: "random" # random 或 specified
|
||||||
@@ -1697,32 +1697,32 @@ queryOptions:
|
|||||||
sourceCluster: "cluster2"
|
sourceCluster: "cluster2"
|
||||||
# 指定Key模式下的键值列表
|
# 指定Key模式下的键值列表
|
||||||
keys:
|
keys:
|
||||||
- "user:1001"
|
- "user:example1"
|
||||||
- "user:1002"`;
|
- "user:example2"`;
|
||||||
} else {
|
} else {
|
||||||
templateElement.textContent = `{
|
templateElement.textContent = `{
|
||||||
"cluster1": {
|
"cluster1": {
|
||||||
"clusterName": "redis-production",
|
"clusterName": "redis-example1",
|
||||||
"clusterAddress": "10.20.2.109:6470",
|
"clusterAddress": "127.0.0.1:6379",
|
||||||
"clusterPassword": "",
|
"clusterPassword": "",
|
||||||
"cachePrefix": "message.status.Reader.",
|
"cachePrefix": "message.status.Reader.",
|
||||||
"cacheTtl": 2000,
|
"cacheTtl": 2000,
|
||||||
"async": true,
|
"async": true,
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{"host": "10.20.2.109", "port": 6470},
|
{"host": "127.0.0.1", "port": 6379},
|
||||||
{"host": "10.20.2.110", "port": 6470}
|
{"host": "127.0.0.2", "port": 6379}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"cluster2": {
|
"cluster2": {
|
||||||
"clusterName": "redis-test",
|
"clusterName": "redis-example2",
|
||||||
"clusterAddress": "10.20.2.109:6471",
|
"clusterAddress": "127.0.0.1:6380",
|
||||||
"clusterPassword": "",
|
"clusterPassword": "",
|
||||||
"cachePrefix": "message.status.Reader.",
|
"cachePrefix": "message.status.Reader.",
|
||||||
"cacheTtl": 2000,
|
"cacheTtl": 2000,
|
||||||
"async": true,
|
"async": true,
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{"host": "10.20.2.109", "port": 6471},
|
{"host": "127.0.0.1", "port": 6380},
|
||||||
{"host": "10.20.2.110", "port": 6471}
|
{"host": "127.0.0.2", "port": 6380}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"queryOptions": {
|
"queryOptions": {
|
||||||
@@ -1730,7 +1730,7 @@ queryOptions:
|
|||||||
"count": 100,
|
"count": 100,
|
||||||
"pattern": "*",
|
"pattern": "*",
|
||||||
"sourceCluster": "cluster2",
|
"sourceCluster": "cluster2",
|
||||||
"keys": ["user:1001", "user:1002"]
|
"keys": ["user:example1", "user:example2"]
|
||||||
}
|
}
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
@@ -475,7 +475,7 @@
|
|||||||
<div class="row mt-2">
|
<div class="row mt-2">
|
||||||
<div class="col-8">
|
<div class="col-8">
|
||||||
<label class="form-label">集群节点 (逗号分隔)</label>
|
<label class="form-label">集群节点 (逗号分隔)</label>
|
||||||
<input type="text" class="form-control form-control-sm" id="pro_hosts" placeholder="10.20.2.22,10.20.2.23">
|
<input type="text" class="form-control form-control-sm" id="pro_hosts" placeholder="127.0.0.1,127.0.0.2">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
<label class="form-label">端口</label>
|
<label class="form-label">端口</label>
|
||||||
@@ -485,7 +485,7 @@
|
|||||||
<div class="row mt-2">
|
<div class="row mt-2">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<label class="form-label">用户名</label>
|
<label class="form-label">用户名</label>
|
||||||
<input type="text" class="form-control form-control-sm" id="pro_username" placeholder="cbase">
|
<input type="text" class="form-control form-control-sm" id="pro_username" placeholder="username">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<label class="form-label">密码</label>
|
<label class="form-label">密码</label>
|
||||||
@@ -495,11 +495,11 @@
|
|||||||
<div class="row mt-2">
|
<div class="row mt-2">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<label class="form-label">Keyspace</label>
|
<label class="form-label">Keyspace</label>
|
||||||
<input type="text" class="form-control form-control-sm" id="pro_keyspace" placeholder="yuqing_skinny">
|
<input type="text" class="form-control form-control-sm" id="pro_keyspace" placeholder="keyspace">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<label class="form-label">表名</label>
|
<label class="form-label">表名</label>
|
||||||
<input type="text" class="form-control form-control-sm" id="pro_table" placeholder="document">
|
<input type="text" class="form-control form-control-sm" id="pro_table" placeholder="tablename">
|
||||||
<small class="form-text text-muted" id="pro_table_hint">完整表名或基础表名(分表时)</small>
|
<small class="form-text text-muted" id="pro_table_hint">完整表名或基础表名(分表时)</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -528,7 +528,7 @@
|
|||||||
<div class="row mt-2">
|
<div class="row mt-2">
|
||||||
<div class="col-8">
|
<div class="col-8">
|
||||||
<label class="form-label">集群节点 (逗号分隔)</label>
|
<label class="form-label">集群节点 (逗号分隔)</label>
|
||||||
<input type="text" class="form-control form-control-sm" id="test_hosts" placeholder="10.20.2.22,10.20.2.23">
|
<input type="text" class="form-control form-control-sm" id="test_hosts" placeholder="127.0.0.1,127.0.0.2">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
<label class="form-label">端口</label>
|
<label class="form-label">端口</label>
|
||||||
@@ -538,7 +538,7 @@
|
|||||||
<div class="row mt-2">
|
<div class="row mt-2">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<label class="form-label">用户名</label>
|
<label class="form-label">用户名</label>
|
||||||
<input type="text" class="form-control form-control-sm" id="test_username" placeholder="cbase">
|
<input type="text" class="form-control form-control-sm" id="test_username" placeholder="username">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<label class="form-label">密码</label>
|
<label class="form-label">密码</label>
|
||||||
@@ -548,11 +548,11 @@
|
|||||||
<div class="row mt-2">
|
<div class="row mt-2">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<label class="form-label">Keyspace</label>
|
<label class="form-label">Keyspace</label>
|
||||||
<input type="text" class="form-control form-control-sm" id="test_keyspace" placeholder="yuqing_skinny">
|
<input type="text" class="form-control form-control-sm" id="test_keyspace" placeholder="keyspace">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<label class="form-label">表名</label>
|
<label class="form-label">表名</label>
|
||||||
<input type="text" class="form-control form-control-sm" id="test_table" placeholder="document_test">
|
<input type="text" class="form-control form-control-sm" id="test_table" placeholder="tablename">
|
||||||
<small class="form-text text-muted" id="test_table_hint">完整表名或基础表名(分表时)</small>
|
<small class="form-text text-muted" id="test_table_hint">完整表名或基础表名(分表时)</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -295,85 +295,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 未来工具规划 -->
|
|
||||||
<div class="row mt-5">
|
|
||||||
<div class="col-12">
|
|
||||||
<div class="text-center mb-4">
|
|
||||||
<h3 class="text-muted">
|
|
||||||
<i class="fas fa-road"></i> 技术路线图
|
|
||||||
</h3>
|
|
||||||
<p class="text-muted">持续完善的数据处理生态系统</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-4">
|
|
||||||
<div class="col-lg-6 col-md-12">
|
|
||||||
<div class="tool-card">
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="feature-badge coming-soon">规划中</div>
|
|
||||||
<div class="tool-icon">
|
|
||||||
<i class="fas fa-file-export"></i>
|
|
||||||
</div>
|
|
||||||
<h3 class="tool-title">数据迁移与同步工具</h3>
|
|
||||||
<p class="tool-description">
|
|
||||||
企业级数据迁移平台,支持多种数据库和存储系统之间的数据传输,
|
|
||||||
提供实时同步、增量更新和数据转换功能。
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tool-features">
|
|
||||||
<h5><i class="fas fa-star text-warning"></i> 计划特性:</h5>
|
|
||||||
<ul>
|
|
||||||
<li><i class="fas fa-clock text-muted"></i> 跨平台数据迁移(MySQL/PostgreSQL/MongoDB)</li>
|
|
||||||
<li><i class="fas fa-clock text-muted"></i> 实时数据同步和CDC支持</li>
|
|
||||||
<li><i class="fas fa-clock text-muted"></i> 数据格式转换和映射配置</li>
|
|
||||||
<li><i class="fas fa-clock text-muted"></i> 断点续传和错误恢复</li>
|
|
||||||
<li><i class="fas fa-clock text-muted"></i> 可视化监控和性能优化</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-center">
|
|
||||||
<button class="tool-btn" disabled style="opacity: 0.6;">
|
|
||||||
<i class="fas fa-clock"></i> 开发中
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-lg-6 col-md-12">
|
|
||||||
<div class="tool-card">
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="feature-badge coming-soon">规划中</div>
|
|
||||||
<div class="tool-icon">
|
|
||||||
<i class="fas fa-chart-line"></i>
|
|
||||||
</div>
|
|
||||||
<h3 class="tool-title">数据质量监控平台</h3>
|
|
||||||
<p class="tool-description">
|
|
||||||
智能数据质量评估系统,自动检测数据完整性、一致性和准确性问题,
|
|
||||||
提供实时监控、告警通知和质量报告生成。
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tool-features">
|
|
||||||
<h5><i class="fas fa-star text-warning"></i> 计划特性:</h5>
|
|
||||||
<ul>
|
|
||||||
<li><i class="fas fa-clock text-muted"></i> 智能数据质量评分算法</li>
|
|
||||||
<li><i class="fas fa-clock text-muted"></i> 异常数据自动检测和标记</li>
|
|
||||||
<li><i class="fas fa-clock text-muted"></i> 实时监控Dashboard和告警</li>
|
|
||||||
<li><i class="fas fa-clock text-muted"></i> 质量趋势分析和预测</li>
|
|
||||||
<li><i class="fas fa-clock text-muted"></i> 自动化数据修复建议</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-center">
|
|
||||||
<button class="tool-btn" disabled style="opacity: 0.6;">
|
|
||||||
<i class="fas fa-clock"></i> 开发中
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@@ -495,7 +495,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="specifiedOptions" class="mt-2" style="display: none;">
|
<div id="specifiedOptions" class="mt-2" style="display: none;">
|
||||||
<label class="form-label">Key列表 (每行一个)</label>
|
<label class="form-label">Key列表 (每行一个)</label>
|
||||||
<textarea class="form-control query-keys" id="specifiedKeys" rows="6" placeholder="输入要查询的Key,每行一个 例如: user:1001 user:1002 session:abc123"></textarea>
|
<textarea class="form-control query-keys" id="specifiedKeys" rows="6" placeholder="输入要查询的Key,每行一个 例如: user:example1 user:example2 session:abc123"></textarea>
|
||||||
<small class="form-text text-muted">支持大批量Key查询,建议单次不超过1000个</small>
|
<small class="form-text text-muted">支持大批量Key查询,建议单次不超过1000个</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -720,7 +720,7 @@
|
|||||||
<div id="textImportSection">
|
<div id="textImportSection">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="configYamlText" class="form-label">YAML配置内容</label>
|
<label for="configYamlText" class="form-label">YAML配置内容</label>
|
||||||
<textarea class="form-control" id="configYamlText" rows="8" placeholder="请粘贴YAML格式的配置内容,例如: clusterName: "redis-test" clusterAddress: "10.20.2.109:6470,10.20.2.109:6570" clusterPassword: """></textarea>
|
<textarea class="form-control" id="configYamlText" rows="8" placeholder="请粘贴YAML格式的配置内容,例如: clusterName: "redis-example" clusterAddress: "127.0.0.1:6379" clusterPassword: """></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user