Build lightweight AI agent admin

This commit is contained in:
Codex
2026-06-08 18:14:59 +08:00
commit e164840f43
2530 changed files with 435693 additions and 0 deletions
+200
View File
@@ -0,0 +1,200 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Author: 臧成龙
@Contact: 939589097@qq.com
@Time: 2025-12-31
@File: dumpdata.py
@Desc: 数据导出脚本 - 类似 Django 的 dumpdata - 使用方法: python scripts/dumpdata.py [app_name] > data.json
"""
"""
数据导出脚本 - 类似 Django 的 dumpdata
使用方法: python scripts/dumpdata.py [app_name] > data.json
"""
import asyncio
import json
import sys
from pathlib import Path
from datetime import datetime
from decimal import Decimal
# 添加项目根目录到 Python 路径
sys.path.insert(0, str(Path(__file__).parent.parent))
from sqlalchemy import inspect
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import AsyncSessionLocal, Base
def auto_import_models():
"""自动导入所有模型"""
import importlib
project_root = Path(__file__).parent.parent
# 需要扫描的目录
scan_dirs = ["zq_demo", "core", "scheduler", "online_dev", "ai_platform"]
for scan_dir in scan_dirs:
scan_path = project_root / scan_dir
if not scan_path.exists():
continue
# 递归查找所有 model.py 文件
for model_file in scan_path.rglob("*model.py"):
# 计算模块路径
relative_path = model_file.relative_to(project_root)
module_path = str(relative_path.with_suffix("")).replace("/", ".").replace("\\", ".")
try:
importlib.import_module(module_path)
print(f"导入模型: {module_path}", file=sys.stderr)
except ImportError as e:
print(f"警告: 导入失败 {module_path}: {e}", file=sys.stderr)
# 自动导入所有模型
auto_import_models()
class DateTimeEncoder(json.JSONEncoder):
"""自定义 JSON 编码器,处理日期时间和 Decimal"""
def default(self, obj):
if isinstance(obj, datetime):
return obj.isoformat()
if isinstance(obj, Decimal):
return float(obj)
# 处理 date 类型
from datetime import date
if isinstance(obj, date):
return obj.isoformat()
return super().default(obj)
async def dump_table(session: AsyncSession, model_class):
"""导出单个表的数据"""
from sqlalchemy import select
result = await session.execute(select(model_class))
items = result.scalars().all()
table_data = []
for item in items:
# 获取所有列
item_dict = {}
for column in inspect(model_class).columns:
value = getattr(item, column.name)
item_dict[column.name] = value
table_data.append({
"model": f"{model_class.__module__}.{model_class.__name__}",
"pk": item.id,
"fields": item_dict
})
return table_data
async def dump_all_data(app_name: str = None, exclude_tables: list = None):
"""导出所有数据或指定应用的数据
Args:
app_name: 应用名称(可选),如 core、scheduler
exclude_tables: 排除的表名列表(可选)
"""
import logging
exclude_tables = exclude_tables or []
# 临时禁用 SQLAlchemy 的日志输出
sqlalchemy_logger = logging.getLogger('sqlalchemy.engine')
original_level = sqlalchemy_logger.level
sqlalchemy_logger.setLevel(logging.WARNING)
all_data = []
try:
async with AsyncSessionLocal() as session:
# 获取所有模型
models = []
for mapper in Base.registry.mappers:
model_class = mapper.class_
# 如果指定了 app_name,只导出该应用的模型
if app_name:
module_name = model_class.__module__
if not module_name.startswith(app_name):
continue
# 排除指定的表
if model_class.__tablename__ in exclude_tables:
print(f"跳过表: {model_class.__tablename__}", file=sys.stderr)
continue
models.append(model_class)
# 按表名排序
models.sort(key=lambda m: m.__tablename__)
# 导出每个表
for model_class in models:
print(f"导出表: {model_class.__tablename__}", file=sys.stderr)
table_data = await dump_table(session, model_class)
all_data.extend(table_data)
print(f" - 导出 {len(table_data)} 条记录", file=sys.stderr)
finally:
# 恢复 SQLAlchemy 日志级别
sqlalchemy_logger.setLevel(original_level)
return all_data
async def main():
"""主函数"""
import argparse
parser = argparse.ArgumentParser(description='导出数据到 JSON 文件')
parser.add_argument('app_name', nargs='?', help='应用名称(可选),如 core、scheduler')
parser.add_argument('-o', '--output', help='输出文件路径(可选),不指定则输出到标准输出')
parser.add_argument('-f', '--force', action='store_true', help='强制覆盖已存在的文件')
parser.add_argument('-e', '--exclude', action='append', default=[],
help='排除的表名,可多次使用,如: -e scheduler_log -e core_operation_log')
args = parser.parse_args()
if args.app_name:
print(f"导出应用: {args.app_name}", file=sys.stderr)
else:
print("导出所有数据", file=sys.stderr)
if args.exclude:
print(f"排除表: {', '.join(args.exclude)}", file=sys.stderr)
data = await dump_all_data(args.app_name, exclude_tables=args.exclude)
# 生成 JSON 字符串
json_str = json.dumps(data, ensure_ascii=False, indent=2, cls=DateTimeEncoder)
# 输出到文件或标准输出
if args.output:
output_path = Path(args.output)
# 检查文件是否存在
if output_path.exists() and not args.force:
print(f"\n错误: 文件已存在: {output_path}", file=sys.stderr)
print("使用 -f 或 --force 参数强制覆盖", file=sys.stderr)
sys.exit(1)
# 写入文件
with open(output_path, 'w', encoding='utf-8') as f:
f.write(json_str)
print(f"\n总计导出 {len(data)} 条记录", file=sys.stderr)
print(f"已保存到: {output_path}", file=sys.stderr)
else:
# 输出到标准输出
print(json_str)
print(f"\n总计导出 {len(data)} 条记录", file=sys.stderr)
if __name__ == "__main__":
asyncio.run(main())