mirror of
https://github.com/rnvm9wjdtj-bot/myaps_api.git
synced 2026-06-02 05:54:40 +00:00
chore: 删除老旧的 migrate 迁移工具脚本
移除了整个 scripts/migrate 目录下的所有迁移相关脚本文件,包括README、多平台启动脚本、各功能迁移脚本等,不再维护这套手动迁移方案。
This commit is contained in:
@@ -1,63 +0,0 @@
|
||||
# Monitor Models 迁移工具
|
||||
|
||||
## 📁 文件说明
|
||||
|
||||
| 文件 | 用途 |
|
||||
|------|------|
|
||||
| `migrate_all_in_one.bat` / `.sh` | ✨ **唯一需要运行的脚本** |
|
||||
| `auto_migrate.py` | 自动迁移脚本 |
|
||||
| `migrate_with_tortoise.py` | Tortoise 表创建脚本 |
|
||||
| `add_log_query_indexes.py` | 日志查询索引优化脚本 |
|
||||
| `monitor_orm_config.py` | ORM 配置 |
|
||||
| `README.md` | 本说明文件 |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 使用方法
|
||||
|
||||
**只需要运行一个文件:**
|
||||
|
||||
**Windows:**
|
||||
```
|
||||
scripts\migrate\migrate_all_in_one.bat
|
||||
```
|
||||
|
||||
**Linux:**
|
||||
```
|
||||
./scripts/migrate/migrate_all_in_one.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 菜单选项
|
||||
|
||||
| 选项 | 功能 | 推荐使用场景 |
|
||||
|------|------|--------------|
|
||||
| **[1]** | Auto Migration(自动迁移) | ✅ **日常新增/更新模型(推荐)** |
|
||||
| **[2]** | Create tables with Tortoise | 仅创建新表 |
|
||||
| **[3]** | Reset migrations | 重置所有迁移 |
|
||||
| **[4]** | Add log query indexes | ✅ **优化日志查询性能** |
|
||||
| **[5]** | Backup only | 仅备份数据库 |
|
||||
| **[Q]** | 退出 | - |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 日常流程
|
||||
|
||||
### 新增/更新模型
|
||||
1. 在 `apps/common/monitor/models.py` 中添加/修改模型
|
||||
2. 运行迁移脚本
|
||||
3. 选择 **[1]**
|
||||
4. 完成!✅
|
||||
|
||||
### 优化日志查询性能
|
||||
1. 运行迁移脚本
|
||||
2. 选择 **[4]**
|
||||
3. 自动创建索引(client_ip, method, timestamp, level 等)
|
||||
4. 完成!✅
|
||||
|
||||
---
|
||||
|
||||
## 💡 推荐
|
||||
|
||||
**日常开发请优先使用 [1] Auto Migration** - 自动备份、更简单、更可靠!
|
||||
@@ -1,248 +0,0 @@
|
||||
"""
|
||||
数据库索引迁移脚本 - 日志查询功能强化
|
||||
任务ID: T1.1
|
||||
|
||||
为支持日志查询功能强化(高级过滤、分页排序),添加以下索引:
|
||||
1. api_requests: client_ip, method, (timestamp, status_code)
|
||||
2. outbound_api_requests: method, (timestamp, status_code)
|
||||
3. system_logs: (timestamp, level)
|
||||
|
||||
用法:
|
||||
# 执行迁移(创建索引)
|
||||
python scripts/migrate/add_log_query_indexes.py --action migrate
|
||||
|
||||
# 回滚迁移(删除索引)
|
||||
python scripts/migrate/add_log_query_indexes.py --action rollback
|
||||
|
||||
# 检查状态
|
||||
python scripts/migrate/add_log_query_indexes.py --action status
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import argparse
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
project_root = Path(__file__).parent.parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from tortoise import Tortoise
|
||||
from tortoise.backends.sqlite.client import SqliteClient
|
||||
from globalobjects import logger
|
||||
|
||||
INDEX_DEFINITIONS = {
|
||||
"api_requests": {
|
||||
"idx_api_requests_client_ip": ["client_ip"],
|
||||
"idx_api_requests_method": ["method"],
|
||||
"idx_api_requests_timestamp_status": ["timestamp", "status_code"],
|
||||
},
|
||||
"outbound_api_requests": {
|
||||
"idx_outbound_method": ["method"],
|
||||
"idx_outbound_timestamp_status": ["timestamp", "status_code"],
|
||||
},
|
||||
"system_logs": {
|
||||
"idx_logs_timestamp_level": ["timestamp", "level"],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
async def get_db_client() -> SqliteClient:
|
||||
"""获取数据库客户端"""
|
||||
from core.settings import SQLITE_FILE
|
||||
|
||||
if not Tortoise._inited:
|
||||
await Tortoise.init(
|
||||
db_url=f"sqlite://{SQLITE_FILE}",
|
||||
modules={"models": ["apps.common.monitor.models"]},
|
||||
)
|
||||
|
||||
return Tortoise.get_connection("default")
|
||||
|
||||
|
||||
async def check_table_exists(client: SqliteClient, table: str) -> bool:
|
||||
"""检查表是否存在"""
|
||||
query = f"SELECT name FROM sqlite_master WHERE type='table' AND name='{table}'"
|
||||
result = await client.execute_query(query)
|
||||
return len(result[1]) > 0
|
||||
|
||||
|
||||
async def check_index_exists(client: SqliteClient, table: str, index_name: str) -> bool:
|
||||
"""检查索引是否存在(SQLite)"""
|
||||
if not await check_table_exists(client, table):
|
||||
return False
|
||||
query = f"SELECT name FROM sqlite_master WHERE type='index' AND tbl_name='{table}' AND name='{index_name}'"
|
||||
result = await client.execute_query(query)
|
||||
return len(result[1]) > 0
|
||||
|
||||
|
||||
async def create_index(client: SqliteClient, table: str, index_name: str, columns: list) -> bool:
|
||||
"""创建索引"""
|
||||
try:
|
||||
if not await check_table_exists(client, table):
|
||||
logger.info(f"表不存在,跳过: {table}")
|
||||
return False
|
||||
|
||||
if await check_index_exists(client, table, index_name):
|
||||
logger.info(f"索引已存在,跳过: {table}.{index_name}")
|
||||
return False
|
||||
|
||||
cols = ", ".join(columns)
|
||||
sql = f"CREATE INDEX {index_name} ON {table} ({cols})"
|
||||
await client.execute_query(sql)
|
||||
logger.success(f"创建索引成功", f"{table}.{index_name}", f"({cols})")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"创建索引失败: {table}.{index_name}", exc_info=True)
|
||||
return False
|
||||
|
||||
|
||||
async def drop_index(client: SqliteClient, table: str, index_name: str) -> bool:
|
||||
"""删除索引"""
|
||||
try:
|
||||
if not await check_index_exists(client, table, index_name):
|
||||
logger.info(f"索引不存在,跳过: {table}.{index_name}")
|
||||
return False
|
||||
|
||||
sql = f"DROP INDEX {index_name}"
|
||||
await client.execute_query(sql)
|
||||
logger.success(f"删除索引成功", f"{table}.{index_name}")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"删除索引失败: {table}.{index_name}", exc_info=True)
|
||||
return False
|
||||
|
||||
|
||||
async def migrate():
|
||||
"""执行迁移:创建所有索引"""
|
||||
logger.start("执行索引迁移", "创建")
|
||||
|
||||
client = await get_db_client()
|
||||
|
||||
created_count = 0
|
||||
skipped_count = 0
|
||||
failed_count = 0
|
||||
|
||||
for table, indexes in INDEX_DEFINITIONS.items():
|
||||
logger.info(f"处理表: {table}")
|
||||
|
||||
# 检查表是否存在
|
||||
if not await check_table_exists(client, table):
|
||||
logger.info(f"表不存在,跳过所有索引: {table}")
|
||||
skipped_count += len(indexes)
|
||||
continue
|
||||
|
||||
for index_name, columns in indexes.items():
|
||||
result = await create_index(client, table, index_name, columns)
|
||||
if result:
|
||||
created_count += 1
|
||||
elif await check_index_exists(client, table, index_name):
|
||||
skipped_count += 1
|
||||
else:
|
||||
failed_count += 1
|
||||
|
||||
logger.stop("索引迁移完成", f"创建:{created_count} 跳过:{skipped_count} 失败:{failed_count}")
|
||||
|
||||
if failed_count > 0:
|
||||
logger.error("部分索引创建失败,请检查日志")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
async def rollback():
|
||||
"""回滚迁移:删除所有索引"""
|
||||
logger.start("执行索引迁移", "回滚")
|
||||
|
||||
client = await get_db_client()
|
||||
|
||||
dropped_count = 0
|
||||
skipped_count = 0
|
||||
failed_count = 0
|
||||
|
||||
for table, indexes in INDEX_DEFINITIONS.items():
|
||||
logger.info(f"处理表: {table}")
|
||||
for index_name in indexes.keys():
|
||||
result = await drop_index(client, table, index_name)
|
||||
if result:
|
||||
dropped_count += 1
|
||||
elif not await check_index_exists(client, table, index_name):
|
||||
skipped_count += 1
|
||||
else:
|
||||
failed_count += 1
|
||||
|
||||
logger.stop("索引回滚完成", f"删除:{dropped_count} 跳过:{skipped_count} 失败:{failed_count}")
|
||||
|
||||
if failed_count > 0:
|
||||
logger.error("部分索引删除失败,请检查日志")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
async def status():
|
||||
"""检查索引状态"""
|
||||
logger.info("检查索引状态...")
|
||||
|
||||
client = await get_db_client()
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("索引状态报告")
|
||||
print("=" * 60)
|
||||
|
||||
total_count = 0
|
||||
existing_count = 0
|
||||
table_not_exists_count = 0
|
||||
|
||||
for table, indexes in INDEX_DEFINITIONS.items():
|
||||
table_exists = await check_table_exists(client, table)
|
||||
print(f"\n表: {table}", "- 存在" if table_exists else "- 不存在")
|
||||
print("-" * 40)
|
||||
|
||||
if not table_exists:
|
||||
table_not_exists_count += len(indexes)
|
||||
for index_name, columns in indexes.items():
|
||||
print(f" {index_name:40} ⊘ 表不存在")
|
||||
continue
|
||||
|
||||
for index_name, columns in indexes.items():
|
||||
exists = await check_index_exists(client, table, index_name)
|
||||
status_str = "✓ 已创建" if exists else "✗ 未创建"
|
||||
print(f" {index_name:40} {status_str}")
|
||||
total_count += 1
|
||||
if exists:
|
||||
existing_count += 1
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print(f"总计: {existing_count}/{total_count} 个索引已创建, {table_not_exists_count} 个索引待表创建")
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
|
||||
async def main():
|
||||
parser = argparse.ArgumentParser(description="日志查询功能强化 - 索引迁移脚本")
|
||||
parser.add_argument(
|
||||
"--action",
|
||||
choices=["migrate", "rollback", "status"],
|
||||
default="status",
|
||||
help="操作类型: migrate(创建), rollback(回滚), status(检查)"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
if args.action == "migrate":
|
||||
success = await migrate()
|
||||
sys.exit(0 if success else 1)
|
||||
elif args.action == "rollback":
|
||||
success = await rollback()
|
||||
sys.exit(0 if success else 1)
|
||||
elif args.action == "status":
|
||||
await status()
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
logger.exception(f"执行失败: {e}")
|
||||
sys.exit(1)
|
||||
finally:
|
||||
if Tortoise._inited:
|
||||
await Tortoise.close_connections()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -1,257 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
智能自动迁移脚本
|
||||
|
||||
功能:
|
||||
1. 自动备份数据库(安全第一)
|
||||
2. 自动检测数据库表结构
|
||||
3. 自动创建缺失的表
|
||||
4. 自动添加缺失的字段
|
||||
5. 保留现有数据
|
||||
6. 无需用户干预,一键完成迁移
|
||||
|
||||
使用方法:
|
||||
python scripts/migrate/auto_migrate.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import asyncio
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
# 设置标准输出为 UTF-8 编码
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
# 添加项目根目录到路径
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
||||
|
||||
from tortoise import Tortoise
|
||||
|
||||
|
||||
def backup_database(db_path):
|
||||
"""备份数据库文件"""
|
||||
backup_dir = Path("backups")
|
||||
backup_dir.mkdir(exist_ok=True)
|
||||
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
backup_path = backup_dir / f"{db_path.stem}_{timestamp}.sqlite3"
|
||||
|
||||
try:
|
||||
shutil.copy2(db_path, backup_path)
|
||||
print(f"✅ 数据库备份成功: {backup_path}")
|
||||
return backup_path
|
||||
except Exception as e:
|
||||
print(f"❌ 数据库备份失败: {e}")
|
||||
return None
|
||||
|
||||
|
||||
async def get_existing_tables(db):
|
||||
"""获取数据库中已存在的表"""
|
||||
# execute_query 返回 (count, rows) 元组
|
||||
count, rows = await db.execute_query("SELECT name FROM sqlite_master WHERE type='table';")
|
||||
return {row[0] for row in rows} if rows else set()
|
||||
|
||||
|
||||
async def get_existing_columns(db, table_name):
|
||||
"""获取指定表中已存在的字段"""
|
||||
# execute_query 返回 (count, rows) 元组
|
||||
count, rows = await db.execute_query(f"PRAGMA table_info({table_name});")
|
||||
return {row[1] for row in rows} if rows else set()
|
||||
|
||||
|
||||
async def get_model_fields(model):
|
||||
"""获取模型定义的字段"""
|
||||
return {field_name: field for field_name, field in model._meta.fields_map.items()}
|
||||
|
||||
|
||||
def get_sqlite_type(field):
|
||||
"""获取字段对应的 SQLite 类型"""
|
||||
field_type = type(field).__name__
|
||||
|
||||
type_mapping = {
|
||||
'IntField': 'INTEGER',
|
||||
'BigIntField': 'BIGINT',
|
||||
'SmallIntField': 'INTEGER',
|
||||
'CharField': 'TEXT',
|
||||
'TextField': 'TEXT',
|
||||
'LongTextField': 'TEXT',
|
||||
'FloatField': 'REAL',
|
||||
'DecimalField': 'REAL',
|
||||
'DatetimeField': 'TIMESTAMP',
|
||||
'DateField': 'DATE',
|
||||
'TimeField': 'TIME',
|
||||
'BooleanField': 'INTEGER',
|
||||
'JSONField': 'TEXT',
|
||||
'UUIDField': 'TEXT',
|
||||
'BinaryField': 'BLOB',
|
||||
}
|
||||
|
||||
return type_mapping.get(field_type)
|
||||
|
||||
|
||||
def format_sql_value(value):
|
||||
"""格式化 SQL 值"""
|
||||
if value is None:
|
||||
return 'NULL'
|
||||
if isinstance(value, str):
|
||||
return f"'{value}'"
|
||||
if isinstance(value, bool):
|
||||
return '1' if value else '0'
|
||||
return str(value)
|
||||
|
||||
|
||||
async def add_missing_columns(db, table_name, model):
|
||||
"""为指定表添加缺失的字段"""
|
||||
existing_columns = await get_existing_columns(db, table_name)
|
||||
model_fields = await get_model_fields(model)
|
||||
|
||||
added_columns = []
|
||||
for field_name, field in model_fields.items():
|
||||
if field_name not in existing_columns:
|
||||
# 跳过主键字段(应该已经存在)
|
||||
if isinstance(field, type) and hasattr(field, 'pk') and field.pk:
|
||||
continue
|
||||
|
||||
# 生成 ALTER TABLE 语句
|
||||
sqlite_type = get_sqlite_type(field)
|
||||
if not sqlite_type:
|
||||
print(f" ⚠️ 无法识别字段类型: {field_name} ({type(field).__name__})")
|
||||
continue
|
||||
|
||||
sql = f"ALTER TABLE {table_name} ADD COLUMN {field_name} {sqlite_type}"
|
||||
|
||||
# 添加默认值(如果有)
|
||||
if hasattr(field, 'default') and field.default is not None and field.default != type(field).NO_DEFAULT:
|
||||
if callable(field.default):
|
||||
try:
|
||||
default_val = field.default()
|
||||
if default_val is not None:
|
||||
sql += f" DEFAULT {format_sql_value(default_val)}"
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
sql += f" DEFAULT {format_sql_value(field.default)}"
|
||||
|
||||
# 如果字段允许为空,添加 NULL
|
||||
if hasattr(field, 'null') and field.null:
|
||||
sql += " NULL"
|
||||
else:
|
||||
sql += " NOT NULL"
|
||||
|
||||
print(f" 添加字段: {field_name}")
|
||||
await db.execute_query(sql)
|
||||
added_columns.append(field_name)
|
||||
|
||||
return added_columns
|
||||
|
||||
|
||||
async def create_table_if_not_exists(db, model):
|
||||
"""如果表不存在,创建表"""
|
||||
table_name = model._meta.db_table
|
||||
existing_tables = await get_existing_tables(db)
|
||||
|
||||
if table_name not in existing_tables:
|
||||
print(f" 创建表: {table_name}")
|
||||
# 使用 Tortoise ORM 的 generate_schemas 方法,safe=True 表示只创建不存在的表
|
||||
await Tortoise.generate_schemas(safe=True)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_user_choice():
|
||||
"""获取用户选择"""
|
||||
print("========================================")
|
||||
print(" 🚀 智能自动迁移脚本")
|
||||
print("========================================")
|
||||
print()
|
||||
print("请选择迁移方式:")
|
||||
print(" 1. 不带备份(快速)")
|
||||
print(" 2. 带备份(安全,推荐)")
|
||||
print()
|
||||
|
||||
while True:
|
||||
choice = input("请输入选项 (1 或 2): ").strip()
|
||||
if choice in ["1", "2"]:
|
||||
return choice == "2" # 返回是否需要备份
|
||||
print("❌ 无效输入,请输入 1 或 2")
|
||||
|
||||
|
||||
async def main():
|
||||
# 获取用户选择
|
||||
need_backup = get_user_choice()
|
||||
|
||||
# 获取数据库路径
|
||||
from core.settings import SQLITE_FILE
|
||||
db_path = Path("storage") / f"{SQLITE_FILE}.sqlite3"
|
||||
|
||||
backup_path = None
|
||||
|
||||
# Step 1: 备份数据库(如果用户选择)
|
||||
if need_backup:
|
||||
print("\n[Step 1] 备份数据库...")
|
||||
backup_path = backup_database(db_path)
|
||||
if not backup_path:
|
||||
print(" ⚠️ 备份失败,继续执行迁移...")
|
||||
else:
|
||||
print("\n[Step 1] 跳过备份(用户选择)")
|
||||
|
||||
# Step 2: 初始化 Tortoise
|
||||
print("\n[Step 2] 连接数据库...")
|
||||
from scripts.migrate.migrate_with_tortoise import monitor_orm_config
|
||||
|
||||
try:
|
||||
await Tortoise.init(config=monitor_orm_config)
|
||||
print(" ✅ 数据库连接成功")
|
||||
except Exception as e:
|
||||
print(f" ❌ 数据库连接失败: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
db = Tortoise.get_connection(SQLITE_FILE)
|
||||
|
||||
# Step 3: 获取所有注册的模型
|
||||
from apps.common.monitor.models import APIRequest, OutboundAPIRequest, SystemLog, FailedOperation, BinlogPosition, ProcessedEvent
|
||||
models = [APIRequest, OutboundAPIRequest, SystemLog, FailedOperation, BinlogPosition, ProcessedEvent]
|
||||
|
||||
print("\n[Step 3] 处理表结构...")
|
||||
|
||||
total_tables_created = 0
|
||||
total_columns_added = 0
|
||||
|
||||
for model in models:
|
||||
table_name = model._meta.db_table
|
||||
print(f"\n 处理表: {table_name}")
|
||||
|
||||
# 创建表(如果不存在)
|
||||
created = await create_table_if_not_exists(db, model)
|
||||
if created:
|
||||
total_tables_created += 1
|
||||
print(f" ✅ 表已创建")
|
||||
else:
|
||||
# 添加缺失字段
|
||||
added_columns = await add_missing_columns(db, table_name, model)
|
||||
if added_columns:
|
||||
total_columns_added += len(added_columns)
|
||||
print(f" ✅ 添加了 {len(added_columns)} 个字段: {', '.join(added_columns)}")
|
||||
else:
|
||||
print(f" ✅ 字段已完整,无需更新")
|
||||
|
||||
# Step 4: 完成
|
||||
print("\n========================================")
|
||||
print(" ✅ 迁移完成!")
|
||||
print("========================================")
|
||||
print(f" 结果统计:")
|
||||
print(f" - 创建表: {total_tables_created} 个")
|
||||
print(f" - 添加字段: {total_columns_added} 个")
|
||||
if backup_path:
|
||||
print(f" - 备份文件: {backup_path}")
|
||||
print("\n 🎉 数据库结构已同步到最新模型定义")
|
||||
|
||||
finally:
|
||||
await Tortoise.close_connections()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -1,266 +0,0 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
set "PYTHON_VENV_DIR=venv"
|
||||
|
||||
echo ========================================
|
||||
echo Monitor Models - Setup Tool
|
||||
echo ========================================
|
||||
echo [INFO] Current dir: %cd%
|
||||
echo [INFO] Script path: %~dp0
|
||||
|
||||
echo.
|
||||
echo [INFO] Checking required files...
|
||||
|
||||
if not exist "%~dp0\..\..\.env" (
|
||||
echo [ERROR] .env file not found!
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo [OK] .env file exists
|
||||
|
||||
for /f "tokens=2 delims==" %%a in ('findstr "^PYTHON_VENV_DIR=" "%~dp0\..\..\.env"') do set "PYTHON_VENV_DIR=%%a"
|
||||
set "PYTHON_VENV_DIR=%PYTHON_VENV_DIR: =%"
|
||||
echo [INFO] Virtual env dir: %PYTHON_VENV_DIR%
|
||||
|
||||
if not exist "%~dp0\..\..\%PYTHON_VENV_DIR%\Scripts\python.exe" (
|
||||
echo [ERROR] Python not found in venv!
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo [OK] Python exists
|
||||
|
||||
echo.
|
||||
|
||||
:menu
|
||||
cls
|
||||
echo ========================================
|
||||
echo Monitor Models - Migration Tool
|
||||
echo ========================================
|
||||
echo.
|
||||
echo Default option [1] runs auto migration
|
||||
echo.
|
||||
echo Please select an operation:
|
||||
echo.
|
||||
echo [1] Auto Migration (Recommended)
|
||||
echo - Backup database automatically
|
||||
echo - Create missing tables
|
||||
echo - Add missing fields
|
||||
echo - Preserve existing data
|
||||
echo.
|
||||
echo [2] Create tables with Tortoise
|
||||
echo - Only create new tables
|
||||
echo.
|
||||
echo [3] Reset migrations
|
||||
echo - Delete all migrations and re-init
|
||||
echo.
|
||||
echo [4] Add log query indexes
|
||||
echo - Optimize log query performance
|
||||
echo.
|
||||
echo [5] Backup only
|
||||
echo - Just backup database
|
||||
echo.
|
||||
echo [Q] Exit
|
||||
echo.
|
||||
echo ========================================
|
||||
set /p choice="Enter option (default: 1): "
|
||||
|
||||
if /i "%choice%"=="" goto :auto_migrate
|
||||
if /i "%choice%"=="1" goto :auto_migrate
|
||||
if /i "%choice%"=="2" goto :tortoise
|
||||
if /i "%choice%"=="3" goto :reset
|
||||
if /i "%choice%"=="4" goto :add_indexes
|
||||
if /i "%choice%"=="5" goto :backup_only
|
||||
if /i "%choice%"=="Q" goto :end
|
||||
if /i "%choice%"=="q" goto :end
|
||||
|
||||
echo [ERROR] Invalid option, please try again
|
||||
pause
|
||||
goto :menu
|
||||
|
||||
:backup_only
|
||||
echo.
|
||||
echo ========================================
|
||||
echo [5] Backup Only
|
||||
echo ========================================
|
||||
call :setup_env
|
||||
if errorlevel 1 goto :end
|
||||
|
||||
echo.
|
||||
echo [Step 1] Checking database file...
|
||||
if not exist "storage\%SQLITE_FILE%.sqlite3" (
|
||||
echo [ERROR] Database not found: storage\%SQLITE_FILE%.sqlite3
|
||||
pause
|
||||
goto :end
|
||||
)
|
||||
|
||||
echo [OK] Database exists
|
||||
|
||||
echo.
|
||||
echo [Step 2] Creating backup...
|
||||
set "BACKUP_DIR=backups"
|
||||
if not exist "%BACKUP_DIR%" mkdir "%BACKUP_DIR%"
|
||||
|
||||
set "TIMESTAMP=%date:~0,4%%date:~5,2%%date:~8,2%_%time:~0,2%%time:~3,2%%time:~6,2%"
|
||||
set "TIMESTAMP=%TIMESTAMP: =0%"
|
||||
set "BACKUP_FILE=%BACKUP_DIR%\%SQLITE_FILE%_%TIMESTAMP%.sqlite3"
|
||||
|
||||
copy "storage\%SQLITE_FILE%.sqlite3" "%BACKUP_FILE%"
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] Backup failed
|
||||
pause
|
||||
goto :end
|
||||
)
|
||||
|
||||
echo [OK] Backup successful: %BACKUP_FILE%
|
||||
|
||||
echo.
|
||||
echo ========================================
|
||||
echo Backup completed!
|
||||
echo ========================================
|
||||
goto :end
|
||||
|
||||
:auto_migrate
|
||||
echo.
|
||||
echo ========================================
|
||||
echo [1] Auto Migration
|
||||
echo ========================================
|
||||
call :setup_env
|
||||
if errorlevel 1 goto :end
|
||||
|
||||
echo.
|
||||
echo [INFO] Running auto migration...
|
||||
echo.
|
||||
|
||||
%PYTHON_VENV_DIR%\Scripts\python.exe scripts\migrate\auto_migrate.py
|
||||
if errorlevel 1 goto :error
|
||||
|
||||
goto :success
|
||||
|
||||
:tortoise
|
||||
echo.
|
||||
echo ========================================
|
||||
echo [2] Create tables with Tortoise
|
||||
echo ========================================
|
||||
call :setup_env
|
||||
if errorlevel 1 goto :end
|
||||
|
||||
echo.
|
||||
echo [INFO] This option only creates new tables
|
||||
|
||||
echo.
|
||||
%PYTHON_VENV_DIR%\Scripts\python.exe scripts\migrate\migrate_with_tortoise.py
|
||||
if errorlevel 1 goto :error
|
||||
goto :success
|
||||
|
||||
:reset
|
||||
echo.
|
||||
echo ========================================
|
||||
echo [3] Reset migrations
|
||||
echo ========================================
|
||||
echo.
|
||||
echo [WARNING] This will delete all migrations!
|
||||
|
||||
choice /C YN /M "Are you sure"
|
||||
|
||||
if errorlevel 2 (
|
||||
echo [INFO] Cancelled
|
||||
goto :end
|
||||
)
|
||||
|
||||
call :setup_env
|
||||
if errorlevel 1 goto :end
|
||||
|
||||
echo.
|
||||
echo [1/2] Deleting migrations...
|
||||
if exist "migrations\monitor_models" (
|
||||
rmdir /s /q "migrations\monitor_models"
|
||||
echo [OK] Deleted
|
||||
)
|
||||
|
||||
if exist "migrations" (
|
||||
for /d %%d in (migrations\*) do rmdir /s /q "%%d"
|
||||
echo [OK] All deleted
|
||||
)
|
||||
|
||||
echo.
|
||||
echo [2/2] Re-creating tables with Tortoise...
|
||||
%PYTHON_VENV_DIR%\Scripts\python.exe scripts\migrate\migrate_with_tortoise.py
|
||||
if errorlevel 1 goto :error
|
||||
|
||||
echo.
|
||||
echo ========================================
|
||||
echo Migrations reset!
|
||||
echo ========================================
|
||||
goto :end
|
||||
|
||||
:add_indexes
|
||||
echo.
|
||||
echo ========================================
|
||||
echo [4] Add Log Query Indexes
|
||||
echo ========================================
|
||||
call :setup_env
|
||||
if errorlevel 1 goto :end
|
||||
|
||||
echo.
|
||||
echo [INFO] Creating indexes for log query optimization...
|
||||
echo.
|
||||
|
||||
%PYTHON_VENV_DIR%\Scripts\python.exe scripts\migrate\add_log_query_indexes.py --action migrate
|
||||
if errorlevel 1 goto :error
|
||||
goto :success
|
||||
|
||||
:setup_env
|
||||
cd /d "%~dp0\..\.."
|
||||
set "PROJECT_DIR="
|
||||
set "SQLITE_FILE=local_data"
|
||||
|
||||
for /f "tokens=2 delims==" %%a in ('findstr "^PROJECT_DIR=" "%~dp0\..\..\.env"') do set "PROJECT_DIR=%%a"
|
||||
for /f "tokens=2 delims==" %%a in ('findstr "^SQLITE_FILE=" "%~dp0\..\..\.env"') do set "SQLITE_FILE=%%a"
|
||||
|
||||
if "%PROJECT_DIR%"=="" (
|
||||
echo [ERROR] PROJECT_DIR not found in .env
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
setx PROJECT_DIR "%PROJECT_DIR%" >nul 2>&1
|
||||
set PROJECT_DIR=%PROJECT_DIR%
|
||||
|
||||
set "SQLITE_FILE=%SQLITE_FILE:.sqlite3=%"
|
||||
|
||||
echo Project Directory: %PROJECT_DIR%
|
||||
echo SQLite File: %SQLITE_FILE%
|
||||
|
||||
if not exist "storage" (
|
||||
echo [INFO] Creating storage...
|
||||
mkdir storage
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] Failed to create storage
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo [OK] Created
|
||||
)
|
||||
|
||||
exit /b 0
|
||||
|
||||
:success
|
||||
echo.
|
||||
echo ========================================
|
||||
echo Operation completed successfully!
|
||||
echo ========================================
|
||||
goto :end
|
||||
|
||||
:error
|
||||
echo.
|
||||
echo ========================================
|
||||
echo Operation failed!
|
||||
echo ========================================
|
||||
pause
|
||||
exit /b 1
|
||||
|
||||
:end
|
||||
echo.
|
||||
pause
|
||||
@@ -1,226 +0,0 @@
|
||||
#!/bin/bash
|
||||
# =====================================================
|
||||
# Monitor Models - Migration Tool (Ubuntu/Linux)
|
||||
# Usage: ./migrate_all_in_one.sh [option]
|
||||
# 1 - Auto Migration (default)
|
||||
# 2 - Create tables with Tortoise
|
||||
# 3 - Reset migrations
|
||||
# 5 - Backup only
|
||||
# =====================================================
|
||||
|
||||
set -e
|
||||
|
||||
# 项目根目录
|
||||
PROJECT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# 查找Python解释器
|
||||
find_python() {
|
||||
if [ -f "$PROJECT_DIR/venv/bin/python" ]; then
|
||||
echo "$PROJECT_DIR/venv/bin/python"
|
||||
return
|
||||
fi
|
||||
if command -v python3 &> /dev/null; then
|
||||
echo "python3"
|
||||
return
|
||||
fi
|
||||
echo "python3"
|
||||
}
|
||||
|
||||
PYTHON_CMD=$(find_python)
|
||||
|
||||
# 从.env读取配置
|
||||
load_env() {
|
||||
if [ -f "$PROJECT_DIR/.env" ]; then
|
||||
export $(grep -v '^#' "$PROJECT_DIR/.env" | grep -v '^$' | xargs)
|
||||
fi
|
||||
}
|
||||
|
||||
load_env
|
||||
|
||||
SQLITE_FILE="${SQLITE_FILE:-local_data}"
|
||||
SQLITE_FILE="${SQLITE_FILE%.sqlite3}"
|
||||
|
||||
# 显示菜单
|
||||
show_menu() {
|
||||
clear
|
||||
echo "========================================"
|
||||
echo " Monitor Models - Migration Tool"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo " Default option [1] runs auto migration"
|
||||
echo ""
|
||||
echo " Please select an operation:"
|
||||
echo ""
|
||||
echo " [1] Auto Migration (Recommended)"
|
||||
echo " - Backup database automatically"
|
||||
echo " - Create missing tables"
|
||||
echo " - Add missing fields"
|
||||
echo " - Preserve existing data"
|
||||
echo ""
|
||||
echo " [2] Create tables with Tortoise"
|
||||
echo " - Only create new tables"
|
||||
echo ""
|
||||
echo " [3] Reset migrations"
|
||||
echo " - Delete all migrations and re-init"
|
||||
echo ""
|
||||
echo " [4] Add log query indexes"
|
||||
echo " - Optimize log query performance"
|
||||
echo ""
|
||||
echo " [5] Backup only"
|
||||
echo " - Just backup database"
|
||||
echo ""
|
||||
echo " [Q] Exit"
|
||||
echo ""
|
||||
echo "========================================"
|
||||
}
|
||||
|
||||
# 备份数据库
|
||||
backup_db() {
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " Backup Only"
|
||||
echo "========================================"
|
||||
|
||||
local db_file="storage/${SQLITE_FILE}.sqlite3"
|
||||
if [ ! -f "$db_file" ]; then
|
||||
echo "[ERROR] Database not found: $db_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "[OK] Database exists"
|
||||
|
||||
local backup_dir="backups"
|
||||
mkdir -p "$backup_dir"
|
||||
|
||||
local timestamp=$(date +%Y%m%d_%H%M%S)
|
||||
local backup_file="${backup_dir}/${SQLITE_FILE}_${timestamp}.sqlite3"
|
||||
|
||||
cp "$db_file" "$backup_file"
|
||||
echo "[OK] Backup successful: $backup_file"
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " Backup completed!"
|
||||
echo "========================================"
|
||||
}
|
||||
|
||||
# 自动迁移
|
||||
auto_migrate() {
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " Auto Migration"
|
||||
echo "========================================"
|
||||
|
||||
echo ""
|
||||
echo "[INFO] Running auto migration..."
|
||||
echo ""
|
||||
|
||||
$PYTHON_CMD scripts/migrate/auto_migrate.py
|
||||
}
|
||||
|
||||
# 使用 Tortoise 创建表
|
||||
tortoise_create() {
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " Create tables with Tortoise"
|
||||
echo "========================================"
|
||||
|
||||
echo ""
|
||||
echo "[INFO] This option only creates new tables"
|
||||
echo ""
|
||||
|
||||
$PYTHON_CMD scripts/migrate/migrate_with_tortoise.py
|
||||
}
|
||||
|
||||
# 重置迁移
|
||||
reset_migrations() {
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " Reset migrations"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo "[WARNING] This will delete all migrations!"
|
||||
|
||||
read -p "Are you sure? (y/N): " confirm
|
||||
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
|
||||
echo "[INFO] Cancelled"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "[1/2] Deleting migrations..."
|
||||
|
||||
if [ -d "migrations/monitor_models" ]; then
|
||||
rm -rf "migrations/monitor_models"
|
||||
echo "[OK] Deleted migrations/monitor_models"
|
||||
fi
|
||||
|
||||
if [ -d "migrations" ]; then
|
||||
rm -rf migrations/*
|
||||
echo "[OK] Cleared migrations directory"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "[2/2] Re-creating tables with Tortoise..."
|
||||
$PYTHON_CMD scripts/migrate/migrate_with_tortoise.py
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " Migrations reset!"
|
||||
echo "========================================"
|
||||
}
|
||||
|
||||
# 添加日志查询索引
|
||||
add_log_indexes() {
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " Add Log Query Indexes"
|
||||
echo "========================================"
|
||||
|
||||
echo ""
|
||||
echo "[INFO] Creating indexes for log query optimization..."
|
||||
echo ""
|
||||
|
||||
$PYTHON_CMD scripts/migrate/add_log_query_indexes.py --action migrate
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
local choice="${1:-}"
|
||||
|
||||
if [ -z "$choice" ]; then
|
||||
show_menu
|
||||
read -p "Enter option (default: 1): " choice
|
||||
choice="${choice:-1}"
|
||||
fi
|
||||
|
||||
case "$choice" in
|
||||
1)
|
||||
auto_migrate
|
||||
;;
|
||||
2)
|
||||
tortoise_create
|
||||
;;
|
||||
3)
|
||||
reset_migrations
|
||||
;;
|
||||
4)
|
||||
add_log_indexes
|
||||
;;
|
||||
5)
|
||||
backup_db
|
||||
;;
|
||||
[Qq])
|
||||
echo "Exit"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "[ERROR] Invalid option: $choice"
|
||||
show_menu
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -1,65 +0,0 @@
|
||||
"""
|
||||
使用 Tortoise ORM 直接生成表的脚本(更可靠)
|
||||
替代 aerich 迁移方案,直接调用 generate_schemas()
|
||||
同时提供 Aerich 迁移所需的配置
|
||||
"""
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
|
||||
# 添加项目路径
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
|
||||
from tortoise import Tortoise
|
||||
from core.database import TORTOISE_ORM_CONFIG
|
||||
from globalobjects import logger as log_config
|
||||
|
||||
logger = log_config.get_logger(__name__)
|
||||
|
||||
# Aerich 迁移所需的配置变量
|
||||
monitor_orm_config = TORTOISE_ORM_CONFIG
|
||||
|
||||
async def main():
|
||||
"""主函数 - 直接生成所有表"""
|
||||
logger.info("=" * 50)
|
||||
logger.info(" Tortoise ORM - 直接生成数据库表")
|
||||
logger.info("=" * 50)
|
||||
|
||||
try:
|
||||
# 初始化 Tortoise ORM
|
||||
logger.info("⏳ 初始化 Tortoise ORM...")
|
||||
await Tortoise.init(config=TORTOISE_ORM_CONFIG)
|
||||
|
||||
# 直接生成所有 schema
|
||||
logger.info("⏳ 生成数据库表 (generate_schemas)...")
|
||||
await Tortoise.generate_schemas(safe=True) # safe=True: 不删除已存在的表
|
||||
|
||||
logger.info("✅ 所有表已生成/更新成功!")
|
||||
|
||||
# 列出所有注册的模型
|
||||
logger.info("")
|
||||
logger.info("📋 已注册的模型:")
|
||||
# 从配置中获取模型信息
|
||||
for app_name, app_config in TORTOISE_ORM_CONFIG['apps'].items():
|
||||
logger.info(f" - App '{app_name}':")
|
||||
models = app_config.get('models', [])
|
||||
for model_path in models:
|
||||
if model_path == 'aerich.models':
|
||||
continue
|
||||
logger.info(f" - {model_path}")
|
||||
|
||||
# 关闭连接
|
||||
await Tortoise.close_connections()
|
||||
logger.info("")
|
||||
logger.info("✅ 数据库连接已关闭")
|
||||
logger.info("=" * 50)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 生成表失败: {e}")
|
||||
import traceback
|
||||
logger.error(f"堆栈信息: {traceback.format_exc()}")
|
||||
raise
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 运行主函数
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user