Build lightweight AI agent admin
This commit is contained in:
@@ -0,0 +1,481 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
数据库管理API(异步版本)
|
||||
"""
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.database import get_db
|
||||
from core.database_manager.schema import (
|
||||
ColumnInfo,
|
||||
ConstraintInfo,
|
||||
DatabaseConfig,
|
||||
DatabaseCreateIn,
|
||||
DatabaseInfo,
|
||||
DatabaseOperationOut,
|
||||
DatabaseRenameIn,
|
||||
DataOperationOut,
|
||||
DeleteDataIn,
|
||||
ExecuteDDLIn,
|
||||
ExecuteDDLOut,
|
||||
ExecuteSQLIn,
|
||||
ExecuteSQLOut,
|
||||
IndexInfo,
|
||||
InsertDataIn,
|
||||
QueryDataIn,
|
||||
QueryDataOut,
|
||||
SchemaCreateIn,
|
||||
SchemaInfo,
|
||||
SchemaOperationOut,
|
||||
SchemaRenameIn,
|
||||
TableInfo,
|
||||
TableStructure,
|
||||
UpdateDataIn,
|
||||
ViewInfo,
|
||||
ViewStructure,
|
||||
)
|
||||
from core.database_manager.service import (
|
||||
AsyncDatabaseManagerService,
|
||||
DatabaseManagerForbidden,
|
||||
DatabaseManagerValidationError,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter(prefix="/database_manager", tags=["数据库管理"])
|
||||
|
||||
|
||||
async def _get_service(db_name: str, db: AsyncSession) -> AsyncDatabaseManagerService:
|
||||
try:
|
||||
return await AsyncDatabaseManagerService.create(db_name, db)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
def _handle_manager_error(exc: Exception) -> None:
|
||||
if isinstance(exc, DatabaseManagerForbidden):
|
||||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
||||
if isinstance(exc, DatabaseManagerValidationError):
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
raise exc
|
||||
|
||||
|
||||
# ============ 数据库配置 ============
|
||||
@router.get("/configs", response_model=List[DatabaseConfig], summary="获取数据库配置列表")
|
||||
async def get_database_configs(db: AsyncSession = Depends(get_db)):
|
||||
"""获取所有配置的数据库连接信息"""
|
||||
return await AsyncDatabaseManagerService.get_database_configs_async(db)
|
||||
|
||||
|
||||
@router.post("/{db_name}/test", summary="测试数据库连接")
|
||||
async def test_database_connection(db_name: str, db: AsyncSession = Depends(get_db)):
|
||||
"""测试指定数据库的连接"""
|
||||
service = await _get_service(db_name, db)
|
||||
return await service.test_connection()
|
||||
|
||||
|
||||
# ============ 数据库管理 ============
|
||||
@router.get("/{db_name}/databases", response_model=List[DatabaseInfo], summary="获取数据库列表")
|
||||
async def get_databases(db_name: str, db: AsyncSession = Depends(get_db)):
|
||||
"""获取所有数据库"""
|
||||
service = await _get_service(db_name, db)
|
||||
return await service.get_databases()
|
||||
|
||||
|
||||
@router.post("/{db_name}/databases", response_model=DatabaseOperationOut, summary="创建数据库")
|
||||
async def create_database(
|
||||
db_name: str,
|
||||
data: DatabaseCreateIn,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""创建新数据库"""
|
||||
try:
|
||||
service = await _get_service(db_name, db)
|
||||
success = await service.create_database(
|
||||
name=data.name,
|
||||
owner=data.owner,
|
||||
encoding=data.encoding,
|
||||
template=data.template,
|
||||
charset=data.charset,
|
||||
collation=data.collation,
|
||||
)
|
||||
return DatabaseOperationOut(
|
||||
success=success,
|
||||
message="数据库创建成功" if success else "数据库创建失败",
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except (DatabaseManagerForbidden, DatabaseManagerValidationError) as e:
|
||||
_handle_manager_error(e)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create database: {e}")
|
||||
return DatabaseOperationOut(success=False, message=str(e))
|
||||
|
||||
|
||||
@router.patch(
|
||||
"/{db_name}/databases/{database_name}",
|
||||
response_model=DatabaseOperationOut,
|
||||
summary="重命名数据库",
|
||||
)
|
||||
async def rename_database(
|
||||
db_name: str,
|
||||
database_name: str,
|
||||
data: DatabaseRenameIn,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""重命名数据库(PostgreSQL / SQL Server)"""
|
||||
try:
|
||||
service = await _get_service(db_name, db)
|
||||
success = await service.rename_database(database_name, data.new_name)
|
||||
return DatabaseOperationOut(
|
||||
success=success,
|
||||
message="数据库重命名成功" if success else "数据库重命名失败",
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except (DatabaseManagerForbidden, DatabaseManagerValidationError) as e:
|
||||
_handle_manager_error(e)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to rename database: {e}")
|
||||
return DatabaseOperationOut(success=False, message=str(e))
|
||||
|
||||
|
||||
@router.delete("/{db_name}/databases/{database_name}", response_model=DatabaseOperationOut, summary="删除数据库")
|
||||
async def drop_database(
|
||||
db_name: str,
|
||||
database_name: str,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""删除数据库"""
|
||||
try:
|
||||
service = await _get_service(db_name, db)
|
||||
success = await service.drop_database(database_name)
|
||||
return DatabaseOperationOut(
|
||||
success=success,
|
||||
message="数据库删除成功" if success else "数据库删除失败",
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except (DatabaseManagerForbidden, DatabaseManagerValidationError) as e:
|
||||
_handle_manager_error(e)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to drop database: {e}")
|
||||
return DatabaseOperationOut(success=False, message=str(e))
|
||||
|
||||
|
||||
# ============ Schema管理(PostgreSQL) ============
|
||||
@router.get("/{db_name}/schemas", response_model=List[SchemaInfo], summary="获取Schema列表")
|
||||
async def get_schemas(
|
||||
db_name: str,
|
||||
database: str = None,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""获取所有Schema(仅PostgreSQL)"""
|
||||
service = await _get_service(db_name, db)
|
||||
return await service.get_schemas(database)
|
||||
|
||||
|
||||
@router.post("/{db_name}/schemas", response_model=SchemaOperationOut, summary="创建Schema")
|
||||
async def create_schema(
|
||||
db_name: str,
|
||||
data: SchemaCreateIn,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""创建 Schema(PostgreSQL / SQL Server)"""
|
||||
try:
|
||||
service = await _get_service(db_name, db)
|
||||
success = await service.create_schema(data.name, data.database, data.owner)
|
||||
return SchemaOperationOut(
|
||||
success=success,
|
||||
message="Schema 创建成功" if success else "Schema 创建失败",
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except (DatabaseManagerForbidden, DatabaseManagerValidationError) as e:
|
||||
_handle_manager_error(e)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create schema: {e}")
|
||||
return SchemaOperationOut(success=False, message=str(e))
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{db_name}/schemas/{schema_name}",
|
||||
response_model=SchemaOperationOut,
|
||||
summary="删除Schema",
|
||||
)
|
||||
async def drop_schema(
|
||||
db_name: str,
|
||||
schema_name: str,
|
||||
database: str = Query(None, description="数据库名"),
|
||||
cascade: bool = Query(True, description="是否 CASCADE"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""删除 Schema(PostgreSQL / SQL Server)"""
|
||||
try:
|
||||
service = await _get_service(db_name, db)
|
||||
success = await service.drop_schema(schema_name, database, cascade)
|
||||
return SchemaOperationOut(
|
||||
success=success,
|
||||
message="Schema 删除成功" if success else "Schema 删除失败",
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except (DatabaseManagerForbidden, DatabaseManagerValidationError) as e:
|
||||
_handle_manager_error(e)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to drop schema: {e}")
|
||||
return SchemaOperationOut(success=False, message=str(e))
|
||||
|
||||
|
||||
@router.patch(
|
||||
"/{db_name}/schemas/{schema_name}",
|
||||
response_model=SchemaOperationOut,
|
||||
summary="重命名Schema",
|
||||
)
|
||||
async def rename_schema(
|
||||
db_name: str,
|
||||
schema_name: str,
|
||||
data: SchemaRenameIn,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""重命名 Schema(仅 PostgreSQL)"""
|
||||
try:
|
||||
service = await _get_service(db_name, db)
|
||||
success = await service.rename_schema(schema_name, data.new_name, data.database)
|
||||
return SchemaOperationOut(
|
||||
success=success,
|
||||
message="Schema 重命名成功" if success else "Schema 重命名失败",
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except (DatabaseManagerForbidden, DatabaseManagerValidationError) as e:
|
||||
_handle_manager_error(e)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to rename schema: {e}")
|
||||
return SchemaOperationOut(success=False, message=str(e))
|
||||
|
||||
|
||||
# ============ 表管理 ============
|
||||
@router.get("/{db_name}/tables", response_model=List[TableInfo], summary="获取表列表")
|
||||
async def get_tables(
|
||||
db_name: str,
|
||||
database: str = Query(None, description="数据库名"),
|
||||
schema_name: str = Query(None, description="Schema名"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""获取指定schema/database的所有表"""
|
||||
service = await _get_service(db_name, db)
|
||||
return await service.get_tables(database=database, schema_name=schema_name)
|
||||
|
||||
|
||||
@router.get("/{db_name}/tables/{table_name}/structure", response_model=TableStructure, summary="获取表结构")
|
||||
async def get_table_structure(
|
||||
db_name: str,
|
||||
table_name: str,
|
||||
database: str = Query(None, description="数据库名"),
|
||||
schema_name: str = Query(None, description="Schema名"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""获取表的详细结构"""
|
||||
service = await _get_service(db_name, db)
|
||||
try:
|
||||
return await service.get_table_structure(
|
||||
table_name, database=database, schema_name=schema_name
|
||||
)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=404, detail=str(e)) from e
|
||||
|
||||
|
||||
@router.get("/{db_name}/tables/{table_name}/ddl", summary="获取表DDL")
|
||||
async def get_table_ddl(
|
||||
db_name: str,
|
||||
table_name: str,
|
||||
database: str = Query(None, description="数据库名"),
|
||||
schema_name: str = Query(None, description="Schema名"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""获取表的DDL语句"""
|
||||
service = await _get_service(db_name, db)
|
||||
ddl = await service.get_table_ddl(table_name, schema_name, database)
|
||||
return {"ddl": ddl}
|
||||
|
||||
|
||||
@router.get("/{db_name}/tables/{table_name}/columns", response_model=List[ColumnInfo], summary="获取表字段")
|
||||
async def get_table_columns(
|
||||
db_name: str,
|
||||
table_name: str,
|
||||
database: str = Query(None, description="数据库名"),
|
||||
schema_name: str = Query(None, description="Schema名"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""获取表的字段信息"""
|
||||
service = await _get_service(db_name, db)
|
||||
return await service.get_table_columns(table_name, schema_name, database)
|
||||
|
||||
|
||||
@router.get("/{db_name}/tables/{table_name}/indexes", response_model=List[IndexInfo], summary="获取表索引")
|
||||
async def get_table_indexes(
|
||||
db_name: str,
|
||||
table_name: str,
|
||||
database: str = Query(None, description="数据库名"),
|
||||
schema_name: str = Query(None, description="Schema名"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""获取表的索引信息"""
|
||||
service = await _get_service(db_name, db)
|
||||
return await service.get_table_indexes(table_name, schema_name, database)
|
||||
|
||||
|
||||
@router.get("/{db_name}/tables/{table_name}/constraints", response_model=List[ConstraintInfo], summary="获取表约束")
|
||||
async def get_table_constraints(
|
||||
db_name: str,
|
||||
table_name: str,
|
||||
database: str = Query(None, description="数据库名"),
|
||||
schema_name: str = Query(None, description="Schema名"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""获取表的约束信息"""
|
||||
service = await _get_service(db_name, db)
|
||||
return await service.get_table_constraints(table_name, schema_name, database)
|
||||
|
||||
|
||||
# ============ 视图管理 ============
|
||||
@router.get("/{db_name}/views", response_model=List[ViewInfo], summary="获取视图列表")
|
||||
async def get_views(
|
||||
db_name: str,
|
||||
database: str = Query(None, description="数据库名"),
|
||||
schema_name: str = Query(None, description="Schema名"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""获取指定schema/database的所有视图"""
|
||||
service = await _get_service(db_name, db)
|
||||
return await service.get_views(database=database, schema_name=schema_name)
|
||||
|
||||
|
||||
@router.get("/{db_name}/views/{view_name}/structure", response_model=ViewStructure, summary="获取视图结构")
|
||||
async def get_view_structure(
|
||||
db_name: str,
|
||||
view_name: str,
|
||||
schema_name: str = Query(None, description="Schema名"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""获取视图的详细结构"""
|
||||
service = await _get_service(db_name, db)
|
||||
try:
|
||||
return await service.get_view_structure(view_name, schema_name)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=404, detail=str(e)) from e
|
||||
|
||||
|
||||
@router.get("/{db_name}/views/{view_name}/definition", summary="获取视图定义")
|
||||
async def get_view_definition(
|
||||
db_name: str,
|
||||
view_name: str,
|
||||
schema_name: str = Query(None, description="Schema名"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""获取视图的定义SQL"""
|
||||
service = await _get_service(db_name, db)
|
||||
definition = await service.get_view_definition(view_name, schema_name)
|
||||
return {"definition": definition}
|
||||
|
||||
|
||||
@router.get("/{db_name}/views/{view_name}/dependencies", response_model=List[str], summary="获取视图依赖")
|
||||
async def get_view_dependencies(
|
||||
db_name: str,
|
||||
view_name: str,
|
||||
schema_name: str = Query(None, description="Schema名"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""获取视图依赖的表列表"""
|
||||
service = await _get_service(db_name, db)
|
||||
return await service.get_view_dependencies(view_name, schema_name)
|
||||
|
||||
|
||||
# ============ 数据查询 ============
|
||||
@router.post("/{db_name}/query", response_model=QueryDataOut, summary="查询表数据")
|
||||
async def query_data(
|
||||
db_name: str,
|
||||
data: QueryDataIn,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""分页查询表数据"""
|
||||
service = await _get_service(db_name, db)
|
||||
return await service.query_data(
|
||||
table_name=data.table_name,
|
||||
schema_name=data.schema_name,
|
||||
database=data.database,
|
||||
page=data.page,
|
||||
page_size=data.page_size,
|
||||
where=data.where,
|
||||
order_by=data.order_by,
|
||||
)
|
||||
|
||||
|
||||
# ============ SQL执行 ============
|
||||
@router.post("/{db_name}/execute", response_model=ExecuteSQLOut, summary="执行SQL")
|
||||
async def execute_sql(
|
||||
db_name: str,
|
||||
data: ExecuteSQLIn,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""执行自定义SQL语句"""
|
||||
service = await _get_service(db_name, db)
|
||||
return await service.execute_sql(data.sql, data.is_query)
|
||||
|
||||
|
||||
# ============ 数据操作 ============
|
||||
@router.post("/{db_name}/data/insert", response_model=DataOperationOut, summary="插入数据")
|
||||
async def insert_data(
|
||||
db_name: str,
|
||||
data: InsertDataIn,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""向表中插入数据"""
|
||||
service = await _get_service(db_name, db)
|
||||
return await service.insert_data(data.table_name, data.data, data.schema_name)
|
||||
|
||||
|
||||
@router.post("/{db_name}/data/update", response_model=DataOperationOut, summary="更新数据")
|
||||
async def update_data(
|
||||
db_name: str,
|
||||
data: UpdateDataIn,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""更新表中的数据"""
|
||||
service = await _get_service(db_name, db)
|
||||
return await service.update_data(
|
||||
data.table_name, data.data, data.where, data.schema_name
|
||||
)
|
||||
|
||||
|
||||
@router.post("/{db_name}/data/delete", response_model=DataOperationOut, summary="删除数据")
|
||||
async def delete_data(
|
||||
db_name: str,
|
||||
data: DeleteDataIn,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""删除表中的数据"""
|
||||
service = await _get_service(db_name, db)
|
||||
return await service.delete_data(data.table_name, data.where, data.schema_name)
|
||||
|
||||
|
||||
# ============ DDL操作 ============
|
||||
@router.post("/{db_name}/execute/ddl", response_model=ExecuteDDLOut, summary="执行DDL语句")
|
||||
async def execute_ddl(
|
||||
db_name: str,
|
||||
data: ExecuteDDLIn,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""执行DDL语句(CREATE TABLE, ALTER TABLE, DROP TABLE等)"""
|
||||
try:
|
||||
service = await _get_service(db_name, db)
|
||||
result = await service.execute_ddl(data.sql, data.database, data.schema_name)
|
||||
return ExecuteDDLOut(**result)
|
||||
except HTTPException:
|
||||
raise
|
||||
except (DatabaseManagerForbidden, DatabaseManagerValidationError) as e:
|
||||
_handle_manager_error(e)
|
||||
Reference in New Issue
Block a user