Compare commits

...

9 Commits

Author SHA1 Message Date
211bcc9066 新增意见展开按钮 删除冗余查看按钮 修复差异字段查询 2025-08-12 16:48:00 +08:00
8c3f3df826 新增一键复制差异主键 2025-08-12 16:38:02 +08:00
8d7c4e3730 修改模版内容 2025-08-12 16:27:21 +08:00
cdf7e36ba3 修改模版内容 2025-08-12 16:27:00 +08:00
fbca92ba77 修改文档 2025-08-11 14:09:57 +08:00
0b1dc6b8ca 修复展示错误 2025-08-11 14:07:10 +08:00
eabca97350 项目打包 2025-08-11 09:34:51 +08:00
01e323a7ba 项目打包 2025-08-11 09:34:45 +08:00
d42cefd9ca 完善页面 2025-08-11 09:34:29 +08:00
25 changed files with 1702 additions and 6348 deletions

132
.dockerignore Normal file
View 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
View File

@@ -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
View 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
View 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
View 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
View File

@@ -35,23 +35,141 @@ BigDataTool是一个功能强大的数据库查询比对工具专门用于Cas
## 🛠️ 安装部署
### 1. 克隆项目
### 快速开始
#### 方式1直接运行推荐开发环境
```bash
# 1. 克隆项目
git clone https://github.com/your-org/BigDataTool.git
cd BigDataTool
```
### 2. 安装依赖
```bash
# 2. 安装依赖
pip install -r requirements.txt
```
### 3. 启动应用
```bash
# 3. 启动应用
python app.py
```
应用将在 `http://localhost:5000` 启动
#### 方式2Docker容器化部署推荐生产环境
```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. 设置查询参数
5. 执行比对分析
## 📊 功能特性
## 🏗️ 系统架构
BigDataTool采用模块化分层架构设计
```
┌─────────────────────────────────────────┐
│ 前端界面层 │
│ (HTML + JavaScript + Bootstrap) │
└─────────────┬───────────────────────────┘
┌─────────────▼───────────────────────────┐
│ API路由层 │
│ (Flask Routes) │
└─────────────┬───────────────────────────┘
┌─────────────▼───────────────────────────┐
│ 业务逻辑层 │
│ ┌─────────────┬─────────────────┐ │
│ │ 查询引擎 │ 比对引擎 │ │
│ │Query Engine │ Comparison │ │
│ └─────────────┴─────────────────┘ │
└─────────────┬───────────────────────────┘
┌─────────────▼───────────────────────────┐
│ 数据访问层 │
│ ┌─────────────┬─────────────────┐ │
│ │ Cassandra │ Redis │ │
│ │ Client │ Client │ │
│ └─────────────┴─────────────────┘ │
└─────────────┬───────────────────────────┘
┌─────────────▼───────────────────────────┐
│ 数据存储层 │
│ ┌──────┬──────┬─────────────────┐ │
│ │SQLite│Cassandra│ Redis │ │
│ │(配置) │ (生产) │ (缓存) │ │
│ └──────┴──────┴─────────────────┘ │
└─────────────────────────────────────────┘
```
### 核心组件
- **查询引擎**: 负责Cassandra和Redis的查询执行
- **比对引擎**: 实现智能数据比对算法
- **配置管理**: SQLite存储的配置持久化
- **日志系统**: 实时查询日志收集和展示
### 数据比对引擎
- **智能JSON比较**自动处理JSON格式差异和嵌套结构
@@ -113,24 +275,24 @@ python app.py
### Cassandra配置
```json
{
"cluster_name": "生产集群",
"hosts": ["192.168.1.100", "192.168.1.101"],
"cluster_name": "示例集群",
"hosts": ["127.0.0.1", "127.0.0.2"],
"port": 9042,
"datacenter": "dc1",
"username": "cassandra",
"password": "password",
"keyspace": "my_keyspace",
"table": "my_table"
"keyspace": "example_keyspace",
"table": "example_table"
}
```
### Redis配置
```json
{
"name": "生产Redis",
"name": "示例Redis",
"nodes": [
{"host": "192.168.1.200", "port": 7000},
{"host": "192.168.1.201", "port": 7001}
{"host": "127.0.0.1", "port": 6379},
{"host": "127.0.0.2", "port": 6379}
],
"password": "redis_password",
"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端点
@@ -178,35 +378,120 @@ python app.py
- `GET /api/query-history` - 获取查询历史
- `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`)
5. 开启 Pull Request
我们欢迎所有形式的贡献!请遵循以下步骤:
### 基本流程
1. **Fork项目**
```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) 文件了解详情
## 👥 作者
## 👥 项目团队
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
View File

@@ -76,6 +76,15 @@ if __name__ == '__main__':
logger.info("=== BigDataTool 启动 ===")
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/* 路径下的所有端点")
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
View 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
View 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 "$@"

View File

@@ -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** - 企业级数据处理与比对解决方案

View File

@@ -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

View File

@@ -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

View File

@@ -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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -68,6 +68,32 @@ def setup_routes(app, query_log_collector):
return render_template('redis_test.html')
# 基础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')
def get_default_config():
return jsonify(DEFAULT_CONFIG)

View File

@@ -83,16 +83,16 @@ def create_connection(config):
使用示例:
config = {
'hosts': ['192.168.1.100', '192.168.1.101'],
'hosts': ['127.0.0.1'],
'port': 9042,
'username': 'cassandra',
'password': 'password',
'keyspace': 'my_keyspace',
'keyspace': 'example_keyspace',
'datacenter': 'dc1'
}
cluster, session = create_connection(config)
if session:
result = session.execute("SELECT * FROM my_table LIMIT 10")
result = session.execute("SELECT * FROM example_table LIMIT 10")
cluster.shutdown()
"""
start_time = time.time()

View File

@@ -279,6 +279,11 @@ def compare_json_arrays(array1, array2):
def format_json_for_display(value):
"""格式化JSON用于显示"""
# 处理None值
if value is None:
return "null"
# 处理非字符串类型
if not isinstance(value, str):
return str(value)

View File

@@ -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['value']) # 'John Doe'
>>> print(result['exists']) # True

View File

@@ -908,35 +908,141 @@ function displayStats(results) {
function displayDifferences() {
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;
}
// 计算分页
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 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 = `
<!-- 字段筛选按钮和操作按钮 -->
<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="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="changeDifferencePageSize(this.value)">
<option value="5" ${differencePageSize == 5 ? 'selected' : ''}>5</option>
<option value="10" ${differencePageSize == 10 ? 'selected' : ''}>10</option>
<option value="20" ${differencePageSize == 20 ? 'selected' : ''}>20</option>
<option value="50" ${differencePageSize == 50 ? 'selected' : ''}>50</option>
<option value="100" ${differencePageSize == 100 ? 'selected' : ''}>100</option>
<option value="5" ${differencePageSize == 5 ? 'selected' : ''}>5</option>
<option value="10" ${differencePageSize == 10 ? 'selected' : ''}>10</option>
<option value="20" ${differencePageSize == 20 ? 'selected' : ''}>20</option>
<option value="50" ${differencePageSize == 50 ? 'selected' : ''}>50</option>
<option value="100" ${differencePageSize == 100 ? 'selected' : ''}>100</option>
<option value="custom_diff">自定义</option>
</select>
<input type="number" class="form-control form-control-sm me-2" style="width: 80px; display: none;"
id="customDiffPageSize" placeholder="数量" min="1" max="1000"
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 class="col-md-6">
@@ -951,94 +1057,124 @@ function displayDifferences() {
<div class="input-group">
<span class="input-group-text"><i class="fas fa-search"></i></span>
<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>
`;
// 显示当前页数据
currentPageData.forEach((diff, index) => {
const globalIndex = startIndex + index + 1;
if (diff.message) {
// 记录不存在的情况
html += `
<div class="difference-item card mb-3 border-warning">
<div class="card-header bg-light">
<div class="row align-items-center">
<div class="col">
<strong>差异 #${globalIndex}</strong>
<span class="badge bg-warning ms-2">记录缺失</span>
</div>
<div class="col-auto">
<button class="btn btn-sm btn-outline-info" onclick='showDifferenceRawData(${JSON.stringify(JSON.stringify(diff.key))})'>
<i class="fas fa-code"></i> 原生数据
</button>
</div>
// 显示当前页的主键组
currentPageKeys.forEach((keyStr, groupIndex) => {
const diffs = groupedDifferences[keyStr];
const globalIndex = startIndex + groupIndex + 1;
// 将字符串化的key解析回对象
let keyObj;
try {
keyObj = JSON.parse(keyStr);
} catch (e) {
// 如果解析失败,假设它本身就是一个简单值
keyObj = keyStr;
}
html += `
<div class="card mb-3 border-primary">
<div class="card-header bg-primary bg-opacity-10">
<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>
<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>
<p class="mb-0 mt-2"><strong>主键:</strong> ${formatCompositeKey(keyObj)}</p>
</div>
`;
} else {
// 字段值差异的情况
const isJson = diff.is_json;
const isArray = diff.is_array;
const jsonClass = isJson ? 'json-field' : '';
html += `
<div class="difference-item card mb-3 border-danger">
<div class="card-header bg-light">
<div class="row align-items-center">
<div class="col">
<strong>差异 #${globalIndex}</strong>
<span class="badge bg-danger ms-2">字段差异</span>
${isJson ? '<span class="badge bg-info ms-2">JSON字段</span>' : ''}
${isArray ? '<span class="badge bg-warning ms-2">数组字段</span>' : ''}
</div>
<div class="col-auto">
<button class="btn btn-sm btn-outline-secondary me-2" type="button"
data-bs-toggle="collapse" data-bs-target="#diffCollapse${globalIndex}">
<i class="fas fa-eye"></i> 查看详情
</button>
<button class="btn btn-sm btn-outline-info" onclick='showDifferenceRawData(${JSON.stringify(JSON.stringify(diff.key))})'>
<i class="fas fa-code"></i> 原生数据
</button>
<div class="collapse show" id="keyGroup${globalIndex}">
<div class="card-body">
`;
// 显示该主键下的所有差异字段
diffs.forEach((diff, diffIndex) => {
if (diff.message) {
// 记录不存在的情况
html += `
<div class="alert alert-warning mb-2">
<i class="fas fa-exclamation-triangle"></i> ${diff.message}
</div>
`;
} else {
// 字段值差异的情况
const isJson = diff.is_json;
const isArray = diff.is_array;
const jsonClass = isJson ? 'json-field' : '';
const fieldId = `field_${globalIndex}_${diffIndex}`;
// 调试:打印差异数据
console.log(`差异 ${diff.field}:`, {
pro_value: diff.pro_value,
test_value: diff.test_value,
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>
<p class="mb-0 mt-2"><strong>主键:</strong> ${formatCompositeKey(diff.key)}</p>
<p class="mb-0"><strong>差异字段:</strong> ${diff.field}</p>
</div>
<div class="collapse" id="diffCollapse${globalIndex}">
<div class="card-body">
<div class="mb-4">
<div class="field-header mb-2">
<i class="fas fa-tag text-primary"></i>
<strong>${diff.field}</strong>
<div class="collapse show" id="${fieldId}">
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-header bg-success bg-opacity-10">
<small class="text-success fw-bold">生产环境</small>
</div>
<div class="card-body p-2">
<pre class="field-value mb-0 ${jsonClass}" style="max-height: 200px; overflow-y: auto;">${escapeHtml(proValue)}</pre>
</div>
</div>
</div>
<!-- 环境对比数据行 -->
<div class="row">
<div class="col-12">
<div class="field-container position-relative">
<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)}
${escapeHtml(diff.test_value)}</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 class="col-md-6">
<div class="card">
<div class="card-header bg-info bg-opacity-10">
<small class="text-info fw-bold">测试环境</small>
</div>
<div class="card-body p-2">
<pre class="field-value mb-0 ${jsonClass}" style="max-height: 200px; overflow-y: auto;">${escapeHtml(testValue)}</pre>
</div>
</div>
</div>
</div>
</div>
</div>
`;
}
});
html += `
</div>
</div>
`;
}
</div>
`;
});
// 底部分页
@@ -1055,12 +1191,41 @@ ${escapeHtml(diff.test_value)}</pre>
}
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
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');
div.textContent = text;
div.textContent = str;
return div.innerHTML;
}
@@ -1068,8 +1233,73 @@ function escapeHtml(text) {
function displayIdenticalResults() {
const identicalContainer = document.getElementById('identical-results');
// 保存当前搜索框的值
const currentSearchValue = document.getElementById('identicalSearch')?.value || '';
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;
}
@@ -1111,7 +1341,15 @@ function displayIdenticalResults() {
<div class="input-group">
<span class="input-group-text"><i class="fas fa-search"></i></span>
<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>
`;
@@ -1128,10 +1366,6 @@ function displayIdenticalResults() {
<span class="badge bg-success ms-2">完全匹配</span>
</div>
<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))})'>
<i class="fas fa-code"></i> 原生数据
</button>
@@ -1139,7 +1373,7 @@ function displayIdenticalResults() {
</div>
<p class="mb-0 mt-2"><strong>主键:</strong> ${formatCompositeKey(result.key)}</p>
</div>
<div class="collapse" id="collapse${globalIndex}">
<div class="collapse show" id="collapse${globalIndex}">
<div class="card-body">
`;
@@ -1219,6 +1453,15 @@ ${escapeHtml(String(testValue))}</pre>
}
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) {
if (!currentResults) return;
// 保存搜索词
currentIdenticalSearchTerm = searchTerm;
if (!searchTerm.trim()) {
filteredIdenticalResults = currentResults.identical_results;
} else {
@@ -1361,6 +1609,15 @@ function searchIdenticalResults(searchTerm) {
displayIdenticalResults();
}
// 清除相同记录搜索
function clearIdenticalSearch() {
currentIdenticalSearchTerm = '';
document.getElementById('identicalSearch').value = '';
filteredIdenticalResults = currentResults.identical_results;
currentIdenticalPage = 1;
displayIdenticalResults();
}
// 生成差异分页导航
function generateDifferencePagination(currentPage, totalPages) {
if (totalPages <= 1) return '';
@@ -1474,14 +1731,28 @@ function handleCustomDiffPageSizeEnter(event) {
}
// 搜索差异结果
let currentSearchTerm = '';
function searchDifferenceResults(searchTerm) {
if (!currentResults) return;
// 保存搜索词
currentSearchTerm = searchTerm;
if (!searchTerm.trim()) {
filteredDifferenceResults = currentResults.differences;
// 如果有字段筛选,应用字段筛选;否则显示全部
if (currentFieldFilter) {
filteredDifferenceResults = currentResults.differences.filter(diff => diff.field === currentFieldFilter);
} else {
filteredDifferenceResults = currentResults.differences;
}
} else {
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();
if (keyStr.includes(term)) return true;
@@ -1504,6 +1775,22 @@ function searchDifferenceResults(searchTerm) {
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) {
navigator.clipboard.writeText(text).then(function() {
@@ -1551,6 +1838,7 @@ function showRawData(keyStr) {
try {
// 解析key
const key = JSON.parse(keyStr);
console.log('查找原始数据key:', key);
// 在原生数据中查找对应的记录
let proData = null;
@@ -1562,15 +1850,40 @@ function showRawData(keyStr) {
// 支持复合主键比较
if (typeof key === 'object' && !Array.isArray(key)) {
// 复合主键情况:比较所有主键字段
return Object.keys(key).every(keyField =>
JSON.stringify(item[keyField]) === JSON.stringify(key[keyField])
);
const matches = Object.keys(key).every(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 {
// 单主键情况:保持原有逻辑
const keyField = Object.keys(key)[0];
return JSON.stringify(item[keyField]) === JSON.stringify(key[keyField]);
// 单主键情况(兼容旧代码)
return false;
}
});
// 如果没找到,输出调试信息
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)) {
// 复合主键情况:比较所有主键字段
return Object.keys(key).every(keyField =>
JSON.stringify(item[keyField]) === JSON.stringify(key[keyField])
);
const matches = Object.keys(key).every(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 {
// 单主键情况:保持原有逻辑
const keyField = Object.keys(key)[0];
return JSON.stringify(item[keyField]) === JSON.stringify(key[keyField]);
// 单主键情况(兼容旧代码)
return false;
}
});
// 如果没找到,输出调试信息
if (!testData) {
console.log('未找到测试数据查找的key:', key);
console.log('可用的数据:', currentResults.raw_test_data.map(item => ({
statusid: item.statusid
})));
}
}
console.log('查找结果 - 生产数据:', proData, '测试数据:', testData);
// 创建模态框内容
const modalContent = `
<div class="modal fade" id="rawDataModal" tabindex="-1">
@@ -1713,14 +2053,47 @@ function showRawData(keyStr) {
// 延迟渲染对比视图和树形视图
setTimeout(() => {
renderDiffView(proData, testData);
renderTreeView('proTreeView', proData);
renderTreeView('testTreeView', testData);
console.log('开始渲染视图,生产数据:', proData, '测试数据:', 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('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);
} 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) {
showRawData(keyStr); // 复用相同的原生数据显示逻辑
@@ -1755,6 +2223,12 @@ function showDifferenceRawData(keyStr) {
function renderDiffView(proData, testData) {
const diffViewContainer = document.getElementById('diffView');
// 确保容器存在
if (!diffViewContainer) {
console.error('对比视图容器不存在');
return;
}
if (!proData && !testData) {
diffViewContainer.innerHTML = '<p class="text-muted text-center">无数据可对比</p>';
return;
@@ -2136,6 +2610,12 @@ function normalizeValue(value) {
function renderTreeView(containerId, data) {
const container = document.getElementById(containerId);
// 确保容器存在
if (!container) {
console.error('树形视图容器不存在:', containerId);
return;
}
if (!data) {
container.innerHTML = '<p class="text-muted">无数据</p>';
return;
@@ -3101,14 +3581,14 @@ function showImportDialog(env) {
<p class="text-muted mb-3">请粘贴配置数据,支持以下格式:</p>
<div class="mb-3">
<small class="text-muted">示例格式:</small>
<pre class="bg-light p-2 rounded small">clusterName: "Hot Cluster"
clusterNodes: "10.20.2.22,10.20.2.23"
<pre class="bg-light p-2 rounded small">clusterName: "Example Cluster"
clusterNodes: "127.0.0.1,127.0.0.2"
port: 9042
datacenter: "cs01"
username: "cbase"
password: "antducbaseadmin@2022"
keyspace: "yuqing_skinny"
table: "status_test"</pre>
datacenter: "dc1"
username: "cassandra"
password: "example_password"
keyspace: "example_keyspace"
table: "example_table"</pre>
</div>
<div class="mb-3">
<label class="form-label">配置数据:</label>
@@ -3954,10 +4434,6 @@ function renderRawDataContent() {
<span class="badge ${envBadgeClass} ms-2">${item.displayName}</span>
</div>
<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))})'>
<i class="fas fa-copy"></i> 复制
</button>
@@ -3965,7 +4441,7 @@ function renderRawDataContent() {
</div>
<p class="mb-0 mt-2"><strong>主键:</strong> ${keyValue}</p>
</div>
<div class="collapse" id="rawCollapse${globalIndex}">
<div class="collapse show" id="rawCollapse${globalIndex}">
<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>
</div>
@@ -4271,4 +4747,69 @@ async function batchDeleteQueryHistory() {
console.error('批量删除Cassandra查询历史记录失败:', error);
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();
}

View File

@@ -1665,30 +1665,30 @@ function updateConfigTemplate() {
if (format === 'yaml') {
templateElement.textContent = `# Redis集群配置示例 (YAML格式)
cluster1:
clusterName: "redis-production"
clusterAddress: "10.20.2.109:6470"
clusterName: "redis-example1"
clusterAddress: "127.0.0.1:6379"
clusterPassword: ""
cachePrefix: "message.status.Reader."
cacheTtl: 2000
async: true
nodes:
- host: "10.20.2.109"
port: 6470
- host: "10.20.2.110"
port: 6470
- host: "127.0.0.1"
port: 6379
- host: "127.0.0.2"
port: 6379
cluster2:
clusterName: "redis-test"
clusterAddress: "10.20.2.109:6471"
clusterName: "redis-example2"
clusterAddress: "127.0.0.1:6380"
clusterPassword: ""
cachePrefix: "message.status.Reader."
cacheTtl: 2000
async: true
nodes:
- host: "10.20.2.109"
port: 6471
- host: "10.20.2.110"
port: 6471
- host: "127.0.0.1"
port: 6380
- host: "127.0.0.2"
port: 6380
queryOptions:
mode: "random" # random 或 specified
@@ -1697,32 +1697,32 @@ queryOptions:
sourceCluster: "cluster2"
# 指定Key模式下的键值列表
keys:
- "user:1001"
- "user:1002"`;
- "user:example1"
- "user:example2"`;
} else {
templateElement.textContent = `{
"cluster1": {
"clusterName": "redis-production",
"clusterAddress": "10.20.2.109:6470",
"clusterName": "redis-example1",
"clusterAddress": "127.0.0.1:6379",
"clusterPassword": "",
"cachePrefix": "message.status.Reader.",
"cacheTtl": 2000,
"async": true,
"nodes": [
{"host": "10.20.2.109", "port": 6470},
{"host": "10.20.2.110", "port": 6470}
{"host": "127.0.0.1", "port": 6379},
{"host": "127.0.0.2", "port": 6379}
]
},
"cluster2": {
"clusterName": "redis-test",
"clusterAddress": "10.20.2.109:6471",
"clusterName": "redis-example2",
"clusterAddress": "127.0.0.1:6380",
"clusterPassword": "",
"cachePrefix": "message.status.Reader.",
"cacheTtl": 2000,
"async": true,
"nodes": [
{"host": "10.20.2.109", "port": 6471},
{"host": "10.20.2.110", "port": 6471}
{"host": "127.0.0.1", "port": 6380},
{"host": "127.0.0.2", "port": 6380}
]
},
"queryOptions": {
@@ -1730,7 +1730,7 @@ queryOptions:
"count": 100,
"pattern": "*",
"sourceCluster": "cluster2",
"keys": ["user:1001", "user:1002"]
"keys": ["user:example1", "user:example2"]
}
}`;
}

View File

@@ -475,7 +475,7 @@
<div class="row mt-2">
<div class="col-8">
<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 class="col-4">
<label class="form-label">端口</label>
@@ -485,7 +485,7 @@
<div class="row mt-2">
<div class="col-6">
<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 class="col-6">
<label class="form-label">密码</label>
@@ -495,11 +495,11 @@
<div class="row mt-2">
<div class="col-6">
<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 class="col-6">
<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>
</div>
</div>
@@ -528,7 +528,7 @@
<div class="row mt-2">
<div class="col-8">
<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 class="col-4">
<label class="form-label">端口</label>
@@ -538,7 +538,7 @@
<div class="row mt-2">
<div class="col-6">
<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 class="col-6">
<label class="form-label">密码</label>
@@ -548,11 +548,11 @@
<div class="row mt-2">
<div class="col-6">
<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 class="col-6">
<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>
</div>
</div>

View File

@@ -295,85 +295,6 @@
</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>

View File

@@ -495,7 +495,7 @@
</div>
<div id="specifiedOptions" class="mt-2" style="display: none;">
<label class="form-label">Key列表 (每行一个)</label>
<textarea class="form-control query-keys" id="specifiedKeys" rows="6" placeholder="输入要查询的Key每行一个&#10;例如:&#10;user:1001&#10;user:1002&#10;session:abc123"></textarea>
<textarea class="form-control query-keys" id="specifiedKeys" rows="6" placeholder="输入要查询的Key每行一个&#10;例如:&#10;user:example1&#10;user:example2&#10;session:abc123"></textarea>
<small class="form-text text-muted">支持大批量Key查询建议单次不超过1000个</small>
</div>
</div>
@@ -720,7 +720,7 @@
<div id="textImportSection">
<div class="mb-3">
<label for="configYamlText" class="form-label">YAML配置内容</label>
<textarea class="form-control" id="configYamlText" rows="8" placeholder="请粘贴YAML格式的配置内容例如&#10;clusterName: &quot;redis-test&quot;&#10;clusterAddress: &quot;10.20.2.109:6470,10.20.2.109:6570&quot;&#10;clusterPassword: &quot;&quot;"></textarea>
<textarea class="form-control" id="configYamlText" rows="8" placeholder="请粘贴YAML格式的配置内容例如&#10;clusterName: &quot;redis-example&quot;&#10;clusterAddress: &quot;127.0.0.1:6379&quot;&#10;clusterPassword: &quot;&quot;"></textarea>
</div>
</div>