更新超级管理员

This commit is contained in:
Ubuntu
2025-12-25 10:20:49 +00:00
parent ca42261b5c
commit f1408e53a3
17 changed files with 2449 additions and 728 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ pydantic-settings==2.1.0
# Database
sqlalchemy==2.0.23
asyncpg==0.29.0
psycopg2
psycopg2-binary==2.9.9
# Redis and cache
redis==5.0.1
+1 -1
View File
@@ -31,7 +31,7 @@ router = APIRouter(prefix="/api/admin", tags=["超级管理员"])
def _verify_admin_permission(principal: dict):
"""验证超级管理员权限"""
role = principal.get("claims", {}).get("role")
if role != "super_admin":
if role not in ["super_admin", "admin"]:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="需要超级管理员权限"
+3
View File
@@ -61,10 +61,13 @@ async def _ensure_test_user(db: AsyncSession) -> User:
if test_user is None:
test_user = User(
id=TEST_USER_ID,
name="测试用户", # 添加name字段
username="test_user",
email="test@taiji-ai.com",
password_hash="", # 添加password_hash字段
hashed_password="",
full_name="测试用户",
role="user", # 添加role字段
is_active=True,
is_admin=False,
)
@@ -424,10 +424,47 @@ async def channel_login(payload: Dict[str, str], db: AsyncSession = Depends(get_
password = payload.get("password") or "temp-pass"
if not email:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="email is required")
# 查找或创建渠道用户
user = await ensure_user(email, password, db)
if user.hashed_password and not verify_password(password, user.hashed_password):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="invalid credentials")
token = create_access_token({"sub": str(user.id), "email": email, "role": "channel_admin"})
# 如果用户没有channel_id,尝试查找或创建对应的渠道
channel_id = user.channel_id
if not channel_id:
# 查找是否有对应的渠道
from models import Channel
result = await db.execute(select(Channel).where(Channel.email == email))
channel = result.scalar_one_or_none()
if not channel:
# 创建一个默认渠道
channel = Channel(
name=f"渠道-{email.split('@')[0]}",
email=email,
password_hash=user.password_hash,
commission_rate=10.0,
channel_credit=0.0,
custom_agent_cpu=2.0,
custom_agent_memory=4.0,
status="active"
)
db.add(channel)
await db.flush()
# 更新用户的channel_id
user.channel_id = channel.id
await db.commit()
await db.refresh(user)
channel_id = channel.id
token = create_access_token({
"sub": str(user.id),
"email": email,
"role": "channel_admin",
"channelId": str(channel_id)
})
return {"token": token, "tokenType": "bearer", "email": email, "expiresIn": 60 * 60}
+40 -4
View File
@@ -86,7 +86,16 @@ class AgentActivity(BaseModel):
class GatewaySelectRequest(BaseModel):
"""选择网关请求"""
gatewayType: str = Field(..., pattern="^(MCP|A2A|API)$")
gatewayType: str
@validator('gatewayType')
def validate_gateway_type(cls, v):
"""验证并转换gateway类型为大写"""
if v:
v = v.upper()
if v not in {'MCP', 'A2A', 'API'}:
raise ValueError('gatewayType must be MCP, A2A, or API')
return v
class CreateAPIRequest(BaseModel):
@@ -100,13 +109,22 @@ class GenerateToolRequest(BaseModel):
"""生成工具请求"""
name: str
description: str
frameworkTemplate: str = Field(..., pattern="^(MCP|A2A|API)$")
frameworkTemplate: str
gateway: str
agentCount: int
cpu: float
memory: float
maxScale: int
model: str
@validator('frameworkTemplate', 'gateway')
def validate_gateway_fields(cls, v):
"""验证并转换gateway相关字段为大写"""
if v:
v = v.upper()
if v not in {'MCP', 'A2A', 'API'}:
raise ValueError('Must be MCP, A2A, or API')
return v
class CreateDataTemplateRequest(BaseModel):
@@ -132,7 +150,16 @@ class DeployAgentRequest(BaseModel):
agentId: str
instances: int
model: str
gateway: str = Field(..., pattern="^(MCP|A2A|API)$")
gateway: str
@validator('gateway')
def validate_gateway(cls, v):
"""验证并转换gateway为大写"""
if v:
v = v.upper()
if v not in {'MCP', 'A2A', 'API'}:
raise ValueError('gateway must be MCP, A2A, or API')
return v
class WorkflowNode(BaseModel):
@@ -147,9 +174,18 @@ class CreateWorkflowRequest(BaseModel):
"""创建工作流请求"""
name: str
description: str
gateway: str = Field(..., pattern="^(MCP|A2A|API)$")
gateway: str
nodes: List[WorkflowNode] = Field(..., max_length=3)
@validator('gateway')
def validate_gateway(cls, v):
"""验证并转换gateway为大写"""
if v:
v = v.upper()
if v not in {'MCP', 'A2A', 'API'}:
raise ValueError('gateway must be MCP, A2A, or API')
return v
@validator('nodes')
def validate_nodes(cls, v):
if len(v) > 3:
+28 -2
View File
@@ -10,13 +10,37 @@ from sqlalchemy.orm import sessionmaker
from sqlalchemy import text
from sqlalchemy.engine import make_url
import logging
from urllib.parse import urlparse, parse_qs, urlencode, urlunparse
from config import settings
from models import Base
logger = logging.getLogger(__name__)
database_url = settings.database_url
def prepare_database_url(url: str) -> str:
"""
处理数据库 URL,移除 asyncpg 不支持的参数(如 sslmode)
"""
if not url or "asyncpg" not in url:
return url
parsed = urlparse(url)
query_params = parse_qs(parsed.query)
# 移除 sslmode 参数(asyncpg 不支持,需要用 ssl connect_args 替代)
if "sslmode" in query_params:
del query_params["sslmode"]
# 重新构建查询字符串
new_query = urlencode(query_params, doseq=True)
new_parsed = parsed._replace(query=new_query)
return urlunparse(new_parsed)
# 处理数据库 URL
database_url = prepare_database_url(settings.database_url)
database_url_obj = make_url(database_url)
engine_kwargs = {
@@ -33,7 +57,9 @@ else:
"pool_recycle": 3600,
})
if database_url_obj.host and database_url_obj.host.endswith("postgres.database.azure.com"):
# Azure Database for PostgreSQL 或任何包含 sslmode 的连接都需要 TLS
if (database_url_obj.host and database_url_obj.host.endswith("postgres.database.azure.com")) or \
"sslmode" in settings.database_url:
# Azure Database for PostgreSQL requires TLS; provide a default SSL context.
ssl_context = ssl.create_default_context()
existing_connect_args = engine_kwargs.get("connect_args") or {}
+1
View File
@@ -9,6 +9,7 @@ pydantic-settings==2.1.0
# 数据库
sqlalchemy==2.0.25
asyncpg==0.29.0
aiosqlite==0.19.0
alembic==1.13.1
psycopg2-binary==2.9.9
+178
View File
@@ -0,0 +1,178 @@
# 数据库管理脚本
本目录包含 taiji-AI-PAD 项目的数据库管理脚本。
## 📁 脚本列表
| 脚本 | 说明 |
|------|------|
| `init_database.py` | 数据库初始化脚本,创建表结构和初始数据 |
| `verify_database.py` | 数据库验证脚本,检查数据库状态和数据完整性 |
| `init_test_accounts.py` | 创建测试账号脚本 |
| `check_accounts.py` | 检查账号配置脚本(无需数据库连接) |
---
## 🚀 快速开始
### 1. 准备环境
```bash
cd services/mcp-server
# 创建虚拟环境(如果没有)
python3 -m venv venv
# 激活虚拟环境
source venv/bin/activate # Linux/Mac
# 或 venv\Scripts\activate # Windows
# 安装依赖
pip install -r requirements.txt
```
### 2. 初始化数据库
```bash
python scripts/init_database.py
```
**可选参数:**
- `--skip-sample-data`: 只创建表结构,不创建初始数据
- `--force`: 强制重新创建所有数据(⚠️ 会删除现有数据)
### 3. 验证数据库
```bash
python scripts/verify_database.py
```
**可选参数:**
- `-v, --verbose`: 显示详细信息
- `--json`: 以JSON格式输出结果
---
## 👤 初始账户
### 管理员账户
| 角色 | 邮箱 | 密码 |
|------|------|------|
| 超级管理员 | superadmin@taiji-ai.com | Admin@123456 |
| 系统管理员 | admin@taiji-ai.com | Admin@123456 |
### 测试用户
| 名称 | 邮箱 | 密码 | 订阅级别 |
|------|------|------|----------|
| 测试用户1 | testuser1@taiji-ai.com | Test@123456 | basic |
| 测试用户2 | testuser2@taiji-ai.com | Test@123456 | professional |
---
## 🔐 登录测试
### 超级管理员登录
```bash
curl -X POST http://localhost:8000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"superadmin@taiji-ai.com","password":"Admin@123456"}'
```
### 测试用户登录
```bash
curl -X POST http://localhost:8000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"testuser1@taiji-ai.com","password":"Test@123456"}'
```
---
## 📊 创建的初始数据
### 渠道
- 默认渠道 (default-channel@taiji-ai.com)
### 工具 (5个)
| 名称 | 类别 | 说明 |
|------|------|------|
| web_search | api | 网络搜索工具 |
| text_completion | llm | 文本补全工具 |
| weather_api | api | 天气查询API |
| code_executor | sandbox | 代码执行工具 |
| document_parser | integration | 文档解析工具 |
### 平台Agent (3个)
| 名称 | 类别 | 说明 |
|------|------|------|
| 通用助手 | general | 通用AI助手 |
| 代码助手 | development | 专业代码助手 |
| 数据分析师 | analytics | 数据分析专家 |
---
## 📋 数据库表结构
初始化脚本会创建以下表:
- `users` - 用户表
- `channels` - 渠道表
- `agents` - Agent表
- `tools` - 工具表
- `sessions` - 会话表
- `executions` - 执行记录表
- `api_keys` - API密钥表
- `billing` - 计费详情表
- `balances` - 用户余额表
- `billing_records` - 计费记录表
- `recharge_records` - 充值记录表
- `audit_logs` - 审计日志表
- `model_providers` - 模型供应商表
- `resource_allocations` - 资源分配表
- `applications` - 申请审批表
- `workflows` - 工作流表
- `channel_agent_quotas` - 渠道Agent配额表
- `provider_models` - 模型提供商表
- `gateway_apis` - 网关API表
- `data_templates` - 数据模板表
---
## ⚠️ 注意事项
1. **首次运行**: 确保数据库连接配置正确(检查 `config.py` 或环境变量)
2. **生产环境**: 不要在生产环境使用 `--force` 参数
3. **密码安全**: 生产环境请修改默认密码
4. **API密钥**: 初始化时生成的API密钥只显示一次,请妥善保管
---
## 🔧 故障排除
### 连接失败
1. 检查数据库服务是否运行
2. 验证数据库连接字符串
3. 确认网络连接正常
### 表已存在
脚本默认不会覆盖已存在的数据,如需重新初始化:
```bash
python scripts/init_database.py --force
```
### 依赖问题
```bash
pip install --upgrade -r requirements.txt
```
---
**版本**: v1.0
**更新日期**: 2025年12月25日
@@ -0,0 +1,674 @@
#!/usr/bin/env python3
"""
数据库初始化脚本
功能:
1. 创建所有数据库表(基于 models.py 中的定义)
2. 创建初始管理员用户
3. 创建默认渠道
4. 创建示例工具
5. 创建测试用户
使用方法:
cd services/mcp-server
python scripts/init_database.py
可选参数:
--skip-sample-data 只创建表结构,不创建初始数据
--force 强制重新创建所有数据(会删除现有数据)
"""
import asyncio
import sys
import os
import argparse
from pathlib import Path
from datetime import datetime
import logging
# 添加项目根目录到Python路径
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, text
from passlib.context import CryptContext
import uuid
# 延迟导入,确保路径已添加
from database import AsyncSessionLocal, engine, init_db, check_db_connection
from models import (
Base, User, Channel, Agent, Tool, Session, APIKey,
ModelProvider, AuditLog, Balance
)
from config import settings
# 配置日志
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# ============== 初始数据定义 ==============
# 默认管理员账户
DEFAULT_ADMINS = [
{
"name": "超级管理员",
"email": "superadmin@taiji-ai.com",
"password": "Admin@123456",
"role": "super_admin",
"subscription_tier": "enterprise",
"balance": 100000.0,
"credit_limit": 500000.0,
"status": "active",
"is_admin": True,
"is_active": True,
},
{
"name": "系统管理员",
"email": "admin@taiji-ai.com",
"password": "Admin@123456",
"role": "admin",
"subscription_tier": "enterprise",
"balance": 50000.0,
"credit_limit": 200000.0,
"status": "active",
"is_admin": True,
"is_active": True,
},
]
# 默认渠道
DEFAULT_CHANNELS = [
{
"name": "默认渠道",
"email": "default-channel@taiji-ai.com",
"password": "Channel@123456",
"commission_rate": 0.1,
"channel_credit": 100000.0,
"custom_agent_cpu": 4.0,
"custom_agent_memory": 8.0,
"status": "active",
},
]
# 示例工具
SAMPLE_TOOLS = [
{
"name": "web_search",
"description": "网络搜索工具,支持全网搜索并返回相关结果",
"category": "api",
"schema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "搜索查询关键词"},
"limit": {"type": "integer", "description": "返回结果数量限制", "default": 10},
"language": {"type": "string", "description": "结果语言", "default": "zh"}
},
"required": ["query"]
},
"endpoint": "https://api.example.com/search",
"method": "POST",
"auth_type": "api_key",
"rate_limit": 100,
"cost_per_call": 0.01,
"is_public": True,
"is_active": True,
},
{
"name": "text_completion",
"description": "文本补全工具,基于LLM生成文本内容",
"category": "llm",
"schema": {
"type": "object",
"properties": {
"prompt": {"type": "string", "description": "输入提示文本"},
"max_tokens": {"type": "integer", "description": "最大生成token数", "default": 150},
"temperature": {"type": "number", "description": "生成温度参数", "default": 0.7},
"model": {"type": "string", "description": "使用的模型", "default": "gpt-4"}
},
"required": ["prompt"]
},
"rate_limit": 60,
"cost_per_call": 0.05,
"is_public": True,
"is_active": True,
},
{
"name": "weather_api",
"description": "天气查询API,获取指定城市的天气信息",
"category": "api",
"schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"], "default": "celsius"}
},
"required": ["city"]
},
"endpoint": "https://api.openweathermap.org/data/2.5/weather",
"method": "GET",
"auth_type": "api_key",
"rate_limit": 1000,
"cost_per_call": 0.001,
"is_public": True,
"is_active": True,
},
{
"name": "code_executor",
"description": "代码执行工具,在安全沙箱中执行代码",
"category": "sandbox",
"schema": {
"type": "object",
"properties": {
"code": {"type": "string", "description": "要执行的代码"},
"language": {"type": "string", "enum": ["python", "javascript", "bash"], "default": "python"},
"timeout": {"type": "integer", "description": "执行超时时间(秒)", "default": 30}
},
"required": ["code"]
},
"rate_limit": 30,
"cost_per_call": 0.1,
"timeout": 60,
"is_public": True,
"is_active": True,
},
{
"name": "document_parser",
"description": "文档解析工具,支持PDF、Word等格式",
"category": "integration",
"schema": {
"type": "object",
"properties": {
"file_url": {"type": "string", "description": "文档URL"},
"format": {"type": "string", "enum": ["pdf", "docx", "txt", "html"], "default": "pdf"},
"extract_images": {"type": "boolean", "description": "是否提取图片", "default": False}
},
"required": ["file_url"]
},
"rate_limit": 50,
"cost_per_call": 0.02,
"is_public": True,
"is_active": True,
},
]
# 平台Agent
PLATFORM_AGENTS = [
{
"name": "通用助手",
"type": "platform",
"description": "通用AI助手,可处理多种任务",
"category": "general",
"role": "通用助手",
"goal": "帮助用户完成各种任务,包括问答、信息检索、文本生成等",
"config": {"max_iterations": 10, "verbose": True},
"tools": ["web_search", "text_completion"],
"capabilities": ["chat", "search", "summarize"],
"cpu": 2.0,
"memory": 4.0,
"max_instances": 100,
"status": "active",
"version": "1.0.0",
},
{
"name": "代码助手",
"type": "platform",
"description": "专业代码助手,帮助编写和调试代码",
"category": "development",
"role": "代码助手",
"goal": "帮助用户编写、调试、优化代码",
"config": {"max_iterations": 20, "verbose": True},
"tools": ["code_executor", "text_completion"],
"capabilities": ["code_generation", "code_review", "debugging"],
"cpu": 4.0,
"memory": 8.0,
"max_instances": 50,
"status": "active",
"version": "1.0.0",
},
{
"name": "数据分析师",
"type": "platform",
"description": "数据分析专家,处理数据分析任务",
"category": "analytics",
"role": "数据分析师",
"goal": "帮助用户进行数据分析、可视化和报告生成",
"config": {"max_iterations": 15, "verbose": True},
"tools": ["code_executor", "document_parser"],
"capabilities": ["data_analysis", "visualization", "reporting"],
"cpu": 4.0,
"memory": 8.0,
"max_instances": 30,
"status": "active",
"version": "1.0.0",
},
]
# 测试用户
TEST_USERS = [
{
"name": "测试用户1",
"email": "testuser1@taiji-ai.com",
"password": "Test@123456",
"role": "user",
"subscription_tier": "basic",
"balance": 1000.0,
"credit_limit": 5000.0,
"status": "active",
},
{
"name": "测试用户2",
"email": "testuser2@taiji-ai.com",
"password": "Test@123456",
"role": "user",
"subscription_tier": "professional",
"balance": 5000.0,
"credit_limit": 20000.0,
"status": "active",
},
]
# ============== 数据库操作函数 ==============
async def create_tables():
"""创建所有数据库表"""
logger.info("正在创建数据库表...")
try:
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
logger.info("✅ 数据库表创建成功")
return True
except Exception as e:
logger.error(f"❌ 创建数据库表失败: {e}")
return False
async def drop_all_tables():
"""删除所有数据库表(危险操作!)"""
logger.warning("⚠️ 正在删除所有数据库表...")
try:
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.drop_all)
logger.info("✅ 所有表已删除")
return True
except Exception as e:
logger.error(f"❌ 删除表失败: {e}")
return False
async def create_channels(session: AsyncSession) -> list:
"""创建默认渠道"""
logger.info("正在创建默认渠道...")
created = []
for channel_data in DEFAULT_CHANNELS:
# 检查是否已存在
result = await session.execute(
select(Channel).where(Channel.email == channel_data["email"])
)
existing = result.scalar_one_or_none()
if existing:
logger.info(f" 渠道 '{channel_data['name']}' 已存在,跳过")
created.append(existing)
continue
password = channel_data.pop("password")
channel = Channel(
**channel_data,
password_hash=pwd_context.hash(password)
)
session.add(channel)
created.append(channel)
logger.info(f" ✅ 创建渠道: {channel_data['name']}")
await session.flush()
return created
async def create_admins(session: AsyncSession, channels: list) -> list:
"""创建管理员用户"""
logger.info("正在创建管理员用户...")
created = []
for admin_data in DEFAULT_ADMINS:
# 检查是否已存在
result = await session.execute(
select(User).where(User.email == admin_data["email"])
)
existing = result.scalar_one_or_none()
if existing:
logger.info(f" 用户 '{admin_data['name']}' 已存在,跳过")
created.append(existing)
continue
password = admin_data.pop("password")
user = User(
**admin_data,
password_hash=pwd_context.hash(password),
channel_id=channels[0].id if channels else None
)
session.add(user)
created.append(user)
logger.info(f" ✅ 创建管理员: {admin_data['name']} ({admin_data['role']})")
await session.flush()
return created
async def create_tools(session: AsyncSession) -> list:
"""创建示例工具"""
logger.info("正在创建示例工具...")
created = []
for tool_data in SAMPLE_TOOLS:
# 检查是否已存在
result = await session.execute(
select(Tool).where(Tool.name == tool_data["name"])
)
existing = result.scalar_one_or_none()
if existing:
logger.info(f" 工具 '{tool_data['name']}' 已存在,跳过")
created.append(existing)
continue
tool = Tool(**tool_data)
session.add(tool)
created.append(tool)
logger.info(f" ✅ 创建工具: {tool_data['name']} ({tool_data['category']})")
await session.flush()
return created
async def create_platform_agents(session: AsyncSession, admins: list) -> list:
"""创建平台Agent"""
logger.info("正在创建平台Agent...")
created = []
# 使用第一个管理员作为owner
owner = admins[0] if admins else None
for agent_data in PLATFORM_AGENTS:
# 检查是否已存在
result = await session.execute(
select(Agent).where(Agent.name == agent_data["name"])
)
existing = result.scalar_one_or_none()
if existing:
logger.info(f" Agent '{agent_data['name']}' 已存在,跳过")
created.append(existing)
continue
agent = Agent(
**agent_data,
owner_id=owner.id if owner else None
)
session.add(agent)
created.append(agent)
logger.info(f" ✅ 创建Agent: {agent_data['name']} ({agent_data['type']})")
await session.flush()
return created
async def create_test_users(session: AsyncSession, channels: list) -> list:
"""创建测试用户"""
logger.info("正在创建测试用户...")
created = []
for user_data in TEST_USERS:
# 检查是否已存在
result = await session.execute(
select(User).where(User.email == user_data["email"])
)
existing = result.scalar_one_or_none()
if existing:
logger.info(f" 用户 '{user_data['name']}' 已存在,跳过")
created.append(existing)
continue
password = user_data.pop("password")
user = User(
**user_data,
password_hash=pwd_context.hash(password),
channel_id=channels[0].id if channels else None
)
session.add(user)
created.append(user)
logger.info(f" ✅ 创建用户: {user_data['name']} ({user_data['role']})")
await session.flush()
return created
async def create_api_keys(session: AsyncSession, users: list) -> list:
"""为管理员创建API密钥"""
logger.info("正在创建API密钥...")
created = []
for user in users[:2]: # 只为前两个管理员创建
# 检查是否已有API密钥
result = await session.execute(
select(APIKey).where(APIKey.user_id == user.id)
)
existing = result.scalar_one_or_none()
if existing:
logger.info(f" 用户 '{user.name}' 已有API密钥,跳过")
continue
# 生成API密钥 - 注意 api_key_prefix 字段只有 10 字符
api_key = f"sk-{uuid.uuid4().hex[:28]}" # 总长度32字符
api_key_hash = pwd_context.hash(api_key)
api_key_prefix = api_key[:10] # 保留前10字符作为前缀
key = APIKey(
user_id=user.id,
api_key_hash=api_key_hash,
api_key_prefix=api_key_prefix,
name=f"{user.name}的API密钥",
is_active=True,
)
session.add(key)
created.append((key, api_key, user.name))
logger.info(f" ✅ 为 {user.name} 创建API密钥")
await session.flush()
return created
async def log_audit(session: AsyncSession, action: str, details: dict):
"""记录审计日志"""
try:
audit = AuditLog(
action=action,
resource_type="system",
resource_id="init",
details=details,
success=True,
)
session.add(audit)
await session.flush()
except Exception as e:
logger.warning(f"记录审计日志失败: {e}")
# ============== 主函数 ==============
async def init_database(skip_sample_data: bool = False, force: bool = False):
"""初始化数据库的主函数"""
print("=" * 70)
print(" taiji-AI-PAD 数据库初始化工具")
print("=" * 70)
print(f" 时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f" 数据库: {settings.database_url[:50]}...")
print("=" * 70)
print()
# 1. 检查数据库连接
logger.info("步骤 1/7: 检查数据库连接...")
if not await check_db_connection():
logger.error("❌ 数据库连接失败!请检查数据库配置和网络连接。")
return False
logger.info("✅ 数据库连接正常")
print()
# 2. 是否强制重新创建
if force:
logger.warning("⚠️ 强制模式:将删除所有现有数据!")
confirm = input("确认删除所有数据?(输入 'yes' 确认): ")
if confirm.lower() != 'yes':
logger.info("操作已取消")
return False
await drop_all_tables()
print()
# 3. 创建数据库表
logger.info("步骤 2/7: 创建数据库表...")
if not await create_tables():
return False
print()
if skip_sample_data:
logger.info("⏭️ 跳过初始数据创建(--skip-sample-data)")
return True
# 4-7. 创建初始数据
async with AsyncSessionLocal() as session:
try:
# 4. 创建渠道
logger.info("步骤 3/7: 创建默认渠道...")
channels = await create_channels(session)
print()
# 5. 创建管理员
logger.info("步骤 4/7: 创建管理员用户...")
admins = await create_admins(session, channels)
print()
# 6. 创建工具
logger.info("步骤 5/7: 创建示例工具...")
tools = await create_tools(session)
print()
# 7. 创建平台Agent
logger.info("步骤 6/7: 创建平台Agent...")
agents = await create_platform_agents(session, admins)
print()
# 8. 创建测试用户
logger.info("步骤 7/7: 创建测试用户和API密钥...")
test_users = await create_test_users(session, channels)
api_keys = await create_api_keys(session, admins)
print()
# 记录审计日志
await log_audit(session, "database_init", {
"channels_created": len([c for c in channels if c.id]),
"admins_created": len(admins),
"tools_created": len(tools),
"agents_created": len(agents),
"test_users_created": len(test_users),
})
# 提交所有更改
await session.commit()
except Exception as e:
logger.error(f"❌ 创建初始数据失败: {e}")
await session.rollback()
return False
# 打印结果摘要
print()
print("=" * 70)
print(" ✅ 数据库初始化完成!")
print("=" * 70)
print()
# 打印管理员账户信息
print("【管理员账户】")
print("-" * 70)
print(f"{'角色':<15} {'邮箱':<35} {'密码':<15}")
print("-" * 70)
for admin in DEFAULT_ADMINS:
print(f"{admin['role']:<15} {admin['email']:<35} Admin@123456")
print()
# 打印API密钥
if api_keys:
print("【API密钥】")
print("-" * 70)
for key, api_key, user_name in api_keys:
print(f"{user_name}: {api_key}")
print("-" * 70)
print("⚠️ 请妥善保管API密钥,密钥只显示一次!")
print()
# 打印测试用户
print("【测试用户】")
print("-" * 70)
for user in TEST_USERS:
print(f"{user['name']}: {user['email']} / Test@123456")
print()
# 打印登录测试命令
print("【登录测试命令】")
print("-" * 70)
print("# 超级管理员登录")
print('curl -X POST http://localhost:8000/api/auth/login \\')
print(' -H "Content-Type: application/json" \\')
print(' -d \'{"email":"superadmin@taiji-ai.com","password":"Admin@123456"}\'')
print()
return True
def main():
"""命令行入口"""
parser = argparse.ArgumentParser(description="taiji-AI-PAD 数据库初始化工具")
parser.add_argument(
"--skip-sample-data",
action="store_true",
help="只创建表结构,不创建初始数据"
)
parser.add_argument(
"--force",
action="store_true",
help="强制重新创建所有数据(会删除现有数据)"
)
args = parser.parse_args()
try:
success = asyncio.run(init_database(
skip_sample_data=args.skip_sample_data,
force=args.force
))
sys.exit(0 if success else 1)
except KeyboardInterrupt:
print("\n操作已取消")
sys.exit(1)
except Exception as e:
logger.error(f"初始化失败: {e}", exc_info=True)
sys.exit(1)
if __name__ == "__main__":
main()
@@ -0,0 +1,582 @@
#!/usr/bin/env python3
"""
数据库验证脚本
功能:
1. 验证数据库连接
2. 验证所有表结构是否正确创建
3. 验证初始数据是否存在
4. 验证用户登录功能
5. 生成验证报告
使用方法:
cd services/mcp-server
python scripts/verify_database.py
可选参数:
--verbose, -v 显示详细信息
--json 以JSON格式输出结果
"""
import asyncio
import sys
import os
import argparse
import json
from pathlib import Path
from datetime import datetime
import logging
# 添加项目根目录到Python路径
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, text, inspect
from passlib.context import CryptContext
# 延迟导入
from database import AsyncSessionLocal, engine, check_db_connection
from models import (
Base, User, Channel, Agent, Tool, Session, APIKey,
Execution, Billing, AuditLog, Balance, ModelProvider,
ResourceAllocation, Application, Workflow, BillingRecord,
RechargeRecord, ChannelAgentQuota, ProviderModel,
GatewayAPI, DataTemplate
)
from config import settings
# 配置日志
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# ============== 验证结果类 ==============
class VerificationResult:
"""验证结果封装"""
def __init__(self):
self.checks = []
self.passed = 0
self.failed = 0
self.warnings = 0
def add_check(self, name: str, passed: bool, message: str = "", warning: bool = False):
"""添加检查结果"""
status = "✅" if passed else ("⚠️" if warning else "❌")
self.checks.append({
"name": name,
"passed": passed,
"warning": warning,
"message": message,
"status": status
})
if passed:
self.passed += 1
elif warning:
self.warnings += 1
else:
self.failed += 1
def summary(self) -> dict:
"""返回摘要"""
return {
"total": len(self.checks),
"passed": self.passed,
"failed": self.failed,
"warnings": self.warnings,
"success_rate": f"{(self.passed / len(self.checks) * 100):.1f}%" if self.checks else "N/A"
}
def to_dict(self) -> dict:
"""转换为字典"""
return {
"summary": self.summary(),
"checks": self.checks,
"timestamp": datetime.now().isoformat()
}
def print_report(self, verbose: bool = False):
"""打印报告"""
print()
print("=" * 70)
print(" 数据库验证报告")
print("=" * 70)
print(f" 时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f" 数据库: {settings.database_url[:50]}...")
print("=" * 70)
print()
for check in self.checks:
if verbose or not check["passed"] or check["warning"]:
print(f" {check['status']} {check['name']}")
if check["message"]:
print(f" {check['message']}")
print()
print("-" * 70)
summary = self.summary()
print(f" 总计: {summary['total']} 项检查")
print(f" 通过: {summary['passed']} ✅")
print(f" 警告: {summary['warnings']} ⚠️")
print(f" 失败: {summary['failed']} ❌")
print(f" 成功率: {summary['success_rate']}")
print("-" * 70)
# ============== 验证函数 ==============
async def verify_connection(result: VerificationResult):
"""验证数据库连接"""
try:
connected = await check_db_connection()
result.add_check(
"数据库连接",
connected,
"连接正常" if connected else "无法连接到数据库"
)
return connected
except Exception as e:
result.add_check("数据库连接", False, str(e))
return False
async def verify_tables(result: VerificationResult):
"""验证表结构"""
expected_tables = [
"users", "channels", "agents", "tools", "sessions",
"executions", "api_keys", "billing", "audit_logs",
"balances", "model_providers", "resource_allocations",
"applications", "workflows", "billing_records",
"recharge_records", "channel_agent_quotas", "provider_models",
"gateway_apis", "data_templates"
]
try:
async with engine.connect() as conn:
# 获取实际存在的表
def get_tables(connection):
inspector = inspect(connection)
return inspector.get_table_names()
actual_tables = await conn.run_sync(get_tables)
# 检查每个预期的表
for table in expected_tables:
exists = table in actual_tables
result.add_check(
f"表 '{table}'",
exists,
"存在" if exists else "不存在",
warning=not exists
)
# 检查是否有额外的表(可能是LiteLLM等创建的)
extra_tables = set(actual_tables) - set(expected_tables)
if extra_tables:
result.add_check(
"额外的表",
True,
f"发现额外表: {', '.join(extra_tables)}",
warning=True
)
return True
except Exception as e:
result.add_check("表结构检查", False, str(e))
return False
async def verify_admin_users(result: VerificationResult):
"""验证管理员用户"""
admin_emails = [
"superadmin@taiji-ai.com",
"admin@taiji-ai.com"
]
try:
async with AsyncSessionLocal() as session:
for email in admin_emails:
query = select(User).where(User.email == email)
res = await session.execute(query)
user = res.scalar_one_or_none()
if user:
result.add_check(
f"管理员 '{email}'",
True,
f"存在, 角色: {user.role}, 状态: {user.status}"
)
else:
result.add_check(
f"管理员 '{email}'",
False,
"用户不存在"
)
# 统计管理员数量
query = select(User).where(User.role.in_(["super_admin", "admin"]))
res = await session.execute(query)
admins = res.scalars().all()
result.add_check(
"管理员数量",
len(admins) >= 1,
f"共 {len(admins)} 个管理员"
)
return True
except Exception as e:
result.add_check("管理员用户检查", False, str(e))
return False
async def verify_channels(result: VerificationResult):
"""验证渠道"""
try:
async with AsyncSessionLocal() as session:
query = select(Channel)
res = await session.execute(query)
channels = res.scalars().all()
if channels:
result.add_check(
"默认渠道",
True,
f"共 {len(channels)} 个渠道"
)
# 验证渠道状态
active_channels = [c for c in channels if c.status == "active"]
result.add_check(
"活跃渠道",
len(active_channels) > 0,
f"共 {len(active_channels)} 个活跃渠道"
)
else:
result.add_check(
"默认渠道",
False,
"没有渠道数据",
warning=True
)
return True
except Exception as e:
result.add_check("渠道检查", False, str(e))
return False
async def verify_tools(result: VerificationResult):
"""验证工具"""
expected_tools = ["web_search", "text_completion", "weather_api"]
try:
async with AsyncSessionLocal() as session:
query = select(Tool)
res = await session.execute(query)
tools = res.scalars().all()
if tools:
result.add_check(
"示例工具",
True,
f"共 {len(tools)} 个工具"
)
# 检查每个预期的工具
tool_names = [t.name for t in tools]
for tool_name in expected_tools:
exists = tool_name in tool_names
if not exists:
result.add_check(
f"工具 '{tool_name}'",
False,
"工具不存在",
warning=True
)
else:
result.add_check(
"示例工具",
False,
"没有工具数据",
warning=True
)
return True
except Exception as e:
result.add_check("工具检查", False, str(e))
return False
async def verify_agents(result: VerificationResult):
"""验证Agent"""
try:
async with AsyncSessionLocal() as session:
query = select(Agent)
res = await session.execute(query)
agents = res.scalars().all()
if agents:
result.add_check(
"平台Agent",
True,
f"共 {len(agents)} 个Agent"
)
# 统计类型
platform_agents = [a for a in agents if a.type == "platform"]
custom_agents = [a for a in agents if a.type == "custom"]
result.add_check(
"Agent类型分布",
True,
f"平台: {len(platform_agents)}, 自定义: {len(custom_agents)}"
)
else:
result.add_check(
"平台Agent",
False,
"没有Agent数据",
warning=True
)
return True
except Exception as e:
result.add_check("Agent检查", False, str(e))
return False
async def verify_password_auth(result: VerificationResult):
"""验证密码认证功能"""
test_cases = [
("superadmin@taiji-ai.com", "Admin@123456", True),
("superadmin@taiji-ai.com", "wrong_password", False),
]
try:
async with AsyncSessionLocal() as session:
for email, password, should_pass in test_cases:
query = select(User).where(User.email == email)
res = await session.execute(query)
user = res.scalar_one_or_none()
if not user:
result.add_check(
f"密码验证 ({email})",
False,
"用户不存在",
warning=True
)
continue
# 验证密码
is_valid = pwd_context.verify(password, user.password_hash)
expected_result = is_valid == should_pass
result.add_check(
f"密码验证 ({email}, {'正确密码' if should_pass else '错误密码'})",
expected_result,
"验证通过" if expected_result else "验证失败"
)
return True
except Exception as e:
result.add_check("密码认证检查", False, str(e))
return False
async def verify_data_integrity(result: VerificationResult):
"""验证数据完整性"""
try:
async with AsyncSessionLocal() as session:
# 检查用户-渠道关联
query = select(User).where(User.channel_id.isnot(None))
res = await session.execute(query)
users_with_channel = res.scalars().all()
for user in users_with_channel:
# 验证渠道是否存在
channel_query = select(Channel).where(Channel.id == user.channel_id)
channel_res = await session.execute(channel_query)
channel = channel_res.scalar_one_or_none()
if not channel:
result.add_check(
f"用户 '{user.email}' 的渠道关联",
False,
f"关联的渠道 {user.channel_id} 不存在"
)
result.add_check(
"用户-渠道关联完整性",
True,
f"已验证 {len(users_with_channel)} 个用户的渠道关联"
)
# 检查Agent-用户关联
query = select(Agent).where(Agent.owner_id.isnot(None))
res = await session.execute(query)
agents_with_owner = res.scalars().all()
for agent in agents_with_owner:
owner_query = select(User).where(User.id == agent.owner_id)
owner_res = await session.execute(owner_query)
owner = owner_res.scalar_one_or_none()
if not owner:
result.add_check(
f"Agent '{agent.name}' 的所有者关联",
False,
f"关联的用户 {agent.owner_id} 不存在"
)
result.add_check(
"Agent-用户关联完整性",
True,
f"已验证 {len(agents_with_owner)} 个Agent的所有者关联"
)
return True
except Exception as e:
result.add_check("数据完整性检查", False, str(e))
return False
async def get_table_stats(result: VerificationResult) -> dict:
"""获取表统计信息"""
stats = {}
tables = [
("users", User),
("channels", Channel),
("agents", Agent),
("tools", Tool),
("sessions", Session),
("api_keys", APIKey),
]
try:
async with AsyncSessionLocal() as session:
for table_name, model in tables:
try:
query = select(model)
res = await session.execute(query)
count = len(res.scalars().all())
stats[table_name] = count
except Exception:
stats[table_name] = "N/A"
return stats
except Exception as e:
logger.error(f"获取统计信息失败: {e}")
return stats
# ============== 主函数 ==============
async def verify_database(verbose: bool = False, output_json: bool = False):
"""验证数据库的主函数"""
result = VerificationResult()
# 1. 验证连接
if not await verify_connection(result):
if output_json:
print(json.dumps(result.to_dict(), indent=2, ensure_ascii=False))
else:
result.print_report(verbose)
return False
# 2. 验证表结构
await verify_tables(result)
# 3. 验证管理员用户
await verify_admin_users(result)
# 4. 验证渠道
await verify_channels(result)
# 5. 验证工具
await verify_tools(result)
# 6. 验证Agent
await verify_agents(result)
# 7. 验证密码认证
await verify_password_auth(result)
# 8. 验证数据完整性
await verify_data_integrity(result)
# 获取统计信息
stats = await get_table_stats(result)
# 输出结果
if output_json:
output = result.to_dict()
output["statistics"] = stats
print(json.dumps(output, indent=2, ensure_ascii=False))
else:
result.print_report(verbose)
# 打印统计信息
print()
print("【数据统计】")
print("-" * 70)
for table, count in stats.items():
print(f" {table:<20}: {count}")
print("-" * 70)
return result.failed == 0
def main():
"""命令行入口"""
parser = argparse.ArgumentParser(description="taiji-AI-PAD 数据库验证工具")
parser.add_argument(
"-v", "--verbose",
action="store_true",
help="显示详细信息"
)
parser.add_argument(
"--json",
action="store_true",
help="以JSON格式输出结果"
)
args = parser.parse_args()
try:
success = asyncio.run(verify_database(
verbose=args.verbose,
output_json=args.json
))
sys.exit(0 if success else 1)
except KeyboardInterrupt:
print("\n操作已取消")
sys.exit(1)
except Exception as e:
if args.json:
print(json.dumps({"error": str(e)}, indent=2))
else:
logger.error(f"验证失败: {e}", exc_info=True)
sys.exit(1)
if __name__ == "__main__":
main()