refactor: 移除 Python 后端源码,全面迁移至 Rust gateway 架构

- 删除全部 Python 源码 (src/) 及 Alembic 迁移脚本,归档至 _deprecated_py_src/
- 重构 Rust gateway ai_pipeline: 拆分 planner/finalize 模块,新增 contracts/adaptation 层
- 重组 handlers 模块为 admin/public/proxy/internal/shared 子模块结构
- 新增 executor 模块,引入 Rust 原生数据库迁移 (aether-data/migrations)
- 简化 CI/Docker 构建流程,移除 base image 二级构建,统一为单一 app image
- 移除 Python 相关基础设施文件 (entrypoint.sh, gunicorn_conf.py, Dockerfile.base)
This commit is contained in:
fawney19
2026-04-03 16:26:16 +08:00
parent 8f26e1a31f
commit 1d9c77522a
868 changed files with 1735 additions and 2433 deletions

View File

@@ -0,0 +1,5 @@
"""Provider 操作 API 模块"""
from .routes import router
__all__ = ["router"]

View File

@@ -0,0 +1,680 @@
"""
Provider 操作 API 路由
提供 Provider 操作相关的 API 端点:
- 架构列表
- 连接管理
- 操作执行(余额查询、签到等)
- 配置管理
"""
from __future__ import annotations
from dataclasses import asdict, dataclass, is_dataclass
from typing import Any
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
from src.api.base.admin_adapter import AdminApiAdapter
from src.api.base.context import ApiRequestContext
from src.api.base.pipeline import get_pipeline
from src.database import get_db
from src.models.database import Provider
from src.services.provider_ops import (
ConnectorAuthType,
ProviderActionType,
ProviderOpsConfig,
ProviderOpsService,
get_registry,
)
router = APIRouter(prefix="/api/admin/provider-ops", tags=["Provider Operations"])
pipeline = get_pipeline()
# ==================== Request/Response Models ====================
class ArchitectureInfo(BaseModel):
"""架构信息"""
architecture_id: str
display_name: str
description: str
credentials_schema: dict[str, Any]
supported_auth_types: list[dict[str, Any]]
supported_actions: list[dict[str, Any]]
default_connector: str | None
class ConnectorConfigRequest(BaseModel):
"""连接器配置请求"""
auth_type: str = Field(..., description="认证类型")
config: dict[str, Any] = Field(default_factory=dict, description="连接器配置")
credentials: dict[str, Any] = Field(default_factory=dict, description="凭据信息")
class ActionConfigRequest(BaseModel):
"""操作配置请求"""
enabled: bool = Field(True, description="是否启用")
config: dict[str, Any] = Field(default_factory=dict, description="操作配置")
class SaveConfigRequest(BaseModel):
"""保存配置请求"""
architecture_id: str = Field("generic_api", description="架构 ID")
base_url: str | None = Field(None, description="API 基础地址")
connector: ConnectorConfigRequest
actions: dict[str, ActionConfigRequest] = Field(default_factory=dict)
schedule: dict[str, str] = Field(default_factory=dict, description="定时任务配置")
class ConnectRequest(BaseModel):
"""连接请求"""
credentials: dict[str, Any] | None = Field(None, description="凭据(可选,使用已保存的)")
class ExecuteActionRequest(BaseModel):
"""执行操作请求"""
config: dict[str, Any] | None = Field(None, description="操作配置(覆盖默认)")
class ConnectionStatusResponse(BaseModel):
"""连接状态响应"""
status: str
auth_type: str
connected_at: str | None
expires_at: str | None
last_error: str | None
class ActionResultResponse(BaseModel):
"""操作结果响应"""
status: str
action_type: str
data: Any | None
message: str | None
executed_at: str
response_time_ms: int | None
cache_ttl_seconds: int
class ProviderOpsStatusResponse(BaseModel):
"""Provider 操作状态响应"""
provider_id: str
is_configured: bool
architecture_id: str | None
connection_status: ConnectionStatusResponse
enabled_actions: list[str]
class ProviderOpsConfigResponse(BaseModel):
"""Provider 操作配置响应(脱敏)"""
provider_id: str
is_configured: bool
architecture_id: str | None = None
base_url: str | None = None
connector: dict[str, Any] | None = None
class VerifyAuthResponse(BaseModel):
"""验证认证响应"""
success: bool
message: str | None = None
data: dict[str, Any] | None = None
updated_credentials: dict[str, Any] | None = None
# ==================== Helper Functions ====================
def _serialize_data(data: Any) -> Any:
"""序列化 dataclass 为字典,用于 JSON 响应"""
if data is None:
return None
if is_dataclass(data) and not isinstance(data, type):
return asdict(data)
return data
def _build_action_result_response(result: Any) -> ActionResultResponse:
return ActionResultResponse(
status=result.status.value,
action_type=result.action_type.value,
data=_serialize_data(result.data),
message=result.message,
executed_at=result.executed_at.isoformat(),
response_time_ms=result.response_time_ms,
cache_ttl_seconds=result.cache_ttl_seconds,
)
def _resolve_provider_base_url(
provider_id: str,
db: Session,
*,
saved_config: ProviderOpsConfig | None = None,
) -> str | None:
base_url = saved_config.base_url if saved_config else None
if base_url:
return base_url
provider = db.query(Provider).filter(Provider.id == provider_id).first()
if not provider:
return None
if provider.endpoints:
for endpoint in provider.endpoints:
if endpoint.base_url:
return endpoint.base_url
provider_config = provider.config or {}
return provider_config.get("base_url") or provider.website
def _list_architectures_response() -> list[dict[str, Any]]:
registry = get_registry()
return registry.to_dict_list()
def _get_architecture_response(architecture_id: str) -> dict[str, Any]:
registry = get_registry()
arch = registry.get(architecture_id)
if not arch:
raise HTTPException(status_code=404, detail=f"架构 {architecture_id} 不存在")
return arch.to_dict()
def _get_provider_ops_status_response(provider_id: str, db: Session) -> ProviderOpsStatusResponse:
service = ProviderOpsService(db)
config = service.get_config(provider_id)
conn_state = service.get_connection_status(provider_id)
enabled_actions = []
if config:
for action_type, action_config in config.actions.items():
if action_config.get("enabled", True):
enabled_actions.append(action_type)
return ProviderOpsStatusResponse(
provider_id=provider_id,
is_configured=config is not None,
architecture_id=config.architecture_id if config else None,
connection_status=ConnectionStatusResponse(
status=conn_state.status.value,
auth_type=conn_state.auth_type.value,
connected_at=conn_state.connected_at.isoformat() if conn_state.connected_at else None,
expires_at=conn_state.expires_at.isoformat() if conn_state.expires_at else None,
last_error=conn_state.last_error,
),
enabled_actions=enabled_actions,
)
def _get_provider_ops_config_response(
provider_id: str,
db: Session,
) -> ProviderOpsConfigResponse:
service = ProviderOpsService(db)
config = service.get_config(provider_id)
if not config:
return ProviderOpsConfigResponse(
provider_id=provider_id,
is_configured=False,
)
masked_credentials = service.get_masked_credentials(config.connector_credentials)
base_url = _resolve_provider_base_url(provider_id, db, saved_config=config)
return ProviderOpsConfigResponse(
provider_id=provider_id,
is_configured=True,
architecture_id=config.architecture_id,
base_url=base_url,
connector={
"auth_type": config.connector_auth_type.value,
"config": config.connector_config,
"credentials": masked_credentials,
},
)
def _save_provider_ops_config_response(
provider_id: str,
payload: SaveConfigRequest,
db: Session,
) -> dict[str, Any]:
service = ProviderOpsService(db)
credentials = service.merge_credentials_with_saved(
provider_id, dict(payload.connector.credentials)
)
config = ProviderOpsConfig(
architecture_id=payload.architecture_id,
base_url=payload.base_url,
connector_auth_type=ConnectorAuthType(payload.connector.auth_type),
connector_config=payload.connector.config,
connector_credentials=credentials,
actions={
action_type: {"enabled": action_config.enabled, "config": action_config.config}
for action_type, action_config in payload.actions.items()
},
schedule=payload.schedule,
)
success = service.save_config(provider_id, config)
if not success:
raise HTTPException(status_code=404, detail="Provider 不存在")
return {"success": True, "message": "配置保存成功"}
async def _verify_provider_auth_response(
provider_id: str,
payload: SaveConfigRequest,
db: Session,
) -> VerifyAuthResponse:
service = ProviderOpsService(db)
base_url = payload.base_url or _resolve_provider_base_url(provider_id, db)
if not base_url:
return VerifyAuthResponse(
success=False,
message="请提供 API 地址",
)
credentials = service.merge_credentials_with_saved(
provider_id, dict(payload.connector.credentials)
)
result = await service.verify_auth(
base_url=base_url,
architecture_id=payload.architecture_id,
auth_type=ConnectorAuthType(payload.connector.auth_type),
config=payload.connector.config,
credentials=credentials,
provider_id=provider_id,
)
return VerifyAuthResponse(
success=result.get("success", False),
message=result.get("message"),
data=result.get("data"),
updated_credentials=result.get("updated_credentials"),
)
def _delete_provider_ops_config_response(provider_id: str, db: Session) -> dict[str, Any]:
service = ProviderOpsService(db)
success = service.delete_config(provider_id)
if not success:
raise HTTPException(status_code=404, detail="Provider 不存在")
return {"success": True, "message": "配置已删除"}
async def _connect_provider_response(
provider_id: str,
payload: ConnectRequest,
db: Session,
) -> dict[str, Any]:
service = ProviderOpsService(db)
success, message = await service.connect(provider_id, payload.credentials)
if not success:
raise HTTPException(status_code=400, detail=message)
return {"success": True, "message": message}
async def _disconnect_provider_response(provider_id: str, db: Session) -> dict[str, Any]:
service = ProviderOpsService(db)
await service.disconnect(provider_id)
return {"success": True, "message": "已断开连接"}
async def _execute_action_response(
provider_id: str,
action_type: str,
payload: ExecuteActionRequest,
db: Session,
) -> ActionResultResponse:
service = ProviderOpsService(db)
try:
action_type_enum = ProviderActionType(action_type)
except ValueError as exc:
raise HTTPException(status_code=400, detail=f"无效的操作类型: {action_type}") from exc
result = await service.execute_action(provider_id, action_type_enum, payload.config)
return _build_action_result_response(result)
async def _get_balance_response(
provider_id: str,
refresh: bool,
db: Session,
) -> ActionResultResponse:
service = ProviderOpsService(db)
result = await service.query_balance_with_cache(provider_id, trigger_refresh=refresh)
return _build_action_result_response(result)
async def _refresh_balance_response(provider_id: str, db: Session) -> ActionResultResponse:
service = ProviderOpsService(db)
result = await service.query_balance(provider_id)
return _build_action_result_response(result)
async def _checkin_response(provider_id: str, db: Session) -> ActionResultResponse:
service = ProviderOpsService(db)
result = await service.checkin(provider_id)
return _build_action_result_response(result)
async def _batch_query_balance_response(
provider_ids: list[str] | None,
db: Session,
) -> dict[str, ActionResultResponse]:
service = ProviderOpsService(db)
results = await service.batch_query_balance(provider_ids)
return {
provider_id: _build_action_result_response(result)
for provider_id, result in results.items()
}
# ==================== Routes ====================
@router.get("/architectures", response_model=list[ArchitectureInfo])
async def list_architectures(request: Request, db: Session = Depends(get_db)) -> Any:
"""获取所有可用的架构"""
adapter = AdminProviderOpsListArchitecturesAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.get("/architectures/{architecture_id}", response_model=ArchitectureInfo)
async def get_architecture(
architecture_id: str,
request: Request,
db: Session = Depends(get_db),
) -> Any:
"""获取指定架构的详情"""
adapter = AdminProviderOpsGetArchitectureAdapter(architecture_id=architecture_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.get("/providers/{provider_id}/status", response_model=ProviderOpsStatusResponse)
async def get_provider_ops_status(
provider_id: str,
request: Request,
db: Session = Depends(get_db),
) -> Any:
"""获取 Provider 的操作状态"""
adapter = AdminProviderOpsStatusAdapter(provider_id=provider_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.get("/providers/{provider_id}/config", response_model=ProviderOpsConfigResponse)
async def get_provider_ops_config(
provider_id: str,
request: Request,
db: Session = Depends(get_db),
) -> Any:
"""获取 Provider 的操作配置(脱敏)"""
adapter = AdminProviderOpsConfigAdapter(provider_id=provider_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.put("/providers/{provider_id}/config")
async def save_provider_ops_config(
provider_id: str,
payload: SaveConfigRequest,
request: Request,
db: Session = Depends(get_db),
) -> Any:
"""保存 Provider 的操作配置"""
adapter = AdminProviderOpsSaveConfigAdapter(provider_id=provider_id, payload=payload)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.post("/providers/{provider_id}/verify", response_model=VerifyAuthResponse)
async def verify_provider_auth(
provider_id: str,
payload: SaveConfigRequest,
request: Request,
db: Session = Depends(get_db),
) -> Any:
"""验证 Provider 认证配置"""
adapter = AdminProviderOpsVerifyAuthAdapter(provider_id=provider_id, payload=payload)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.delete("/providers/{provider_id}/config")
async def delete_provider_ops_config(
provider_id: str,
request: Request,
db: Session = Depends(get_db),
) -> Any:
"""删除 Provider 的操作配置"""
adapter = AdminProviderOpsDeleteConfigAdapter(provider_id=provider_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.post("/providers/{provider_id}/connect")
async def connect_provider(
provider_id: str,
payload: ConnectRequest,
request: Request,
db: Session = Depends(get_db),
) -> Any:
"""建立与 Provider 的连接"""
adapter = AdminProviderOpsConnectAdapter(provider_id=provider_id, payload=payload)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.post("/providers/{provider_id}/disconnect")
async def disconnect_provider(
provider_id: str,
request: Request,
db: Session = Depends(get_db),
) -> Any:
"""断开与 Provider 的连接"""
adapter = AdminProviderOpsDisconnectAdapter(provider_id=provider_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.post(
"/providers/{provider_id}/actions/{action_type}",
response_model=ActionResultResponse,
)
async def execute_action(
provider_id: str,
action_type: str,
payload: ExecuteActionRequest,
request: Request,
db: Session = Depends(get_db),
) -> Any:
"""执行指定操作"""
adapter = AdminProviderOpsExecuteActionAdapter(
provider_id=provider_id,
action_type=action_type,
payload=payload,
)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.get("/providers/{provider_id}/balance", response_model=ActionResultResponse)
async def get_balance(
provider_id: str,
request: Request,
refresh: bool = True,
db: Session = Depends(get_db),
) -> Any:
"""获取余额(优先返回缓存,后台异步刷新)"""
adapter = AdminProviderOpsGetBalanceAdapter(provider_id=provider_id, refresh=refresh)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.post("/providers/{provider_id}/balance", response_model=ActionResultResponse)
async def refresh_balance(
provider_id: str,
request: Request,
db: Session = Depends(get_db),
) -> Any:
"""立即刷新余额(同步等待结果)"""
adapter = AdminProviderOpsRefreshBalanceAdapter(provider_id=provider_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.post("/providers/{provider_id}/checkin", response_model=ActionResultResponse)
async def checkin(
provider_id: str,
request: Request,
db: Session = Depends(get_db),
) -> Any:
"""签到(快捷方法)"""
adapter = AdminProviderOpsCheckinAdapter(provider_id=provider_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.post("/batch/balance")
async def batch_query_balance(
request: Request,
provider_ids: list[str] | None = Query(None),
db: Session = Depends(get_db),
) -> Any:
"""批量查询余额"""
adapter = AdminProviderOpsBatchBalanceAdapter(provider_ids=provider_ids)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
# ==================== Adapters ====================
class AdminProviderOpsListArchitecturesAdapter(AdminApiAdapter):
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
return _list_architectures_response()
@dataclass
class AdminProviderOpsGetArchitectureAdapter(AdminApiAdapter):
architecture_id: str
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
return _get_architecture_response(self.architecture_id)
@dataclass
class AdminProviderOpsStatusAdapter(AdminApiAdapter):
provider_id: str
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
return _get_provider_ops_status_response(self.provider_id, context.db)
@dataclass
class AdminProviderOpsConfigAdapter(AdminApiAdapter):
provider_id: str
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
return _get_provider_ops_config_response(self.provider_id, context.db)
@dataclass
class AdminProviderOpsSaveConfigAdapter(AdminApiAdapter):
provider_id: str
payload: SaveConfigRequest
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
return _save_provider_ops_config_response(self.provider_id, self.payload, context.db)
@dataclass
class AdminProviderOpsVerifyAuthAdapter(AdminApiAdapter):
provider_id: str
payload: SaveConfigRequest
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
return await _verify_provider_auth_response(self.provider_id, self.payload, context.db)
@dataclass
class AdminProviderOpsDeleteConfigAdapter(AdminApiAdapter):
provider_id: str
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
return _delete_provider_ops_config_response(self.provider_id, context.db)
@dataclass
class AdminProviderOpsConnectAdapter(AdminApiAdapter):
provider_id: str
payload: ConnectRequest
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
return await _connect_provider_response(self.provider_id, self.payload, context.db)
@dataclass
class AdminProviderOpsDisconnectAdapter(AdminApiAdapter):
provider_id: str
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
return await _disconnect_provider_response(self.provider_id, context.db)
@dataclass
class AdminProviderOpsExecuteActionAdapter(AdminApiAdapter):
provider_id: str
action_type: str
payload: ExecuteActionRequest
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
return await _execute_action_response(
self.provider_id,
self.action_type,
self.payload,
context.db,
)
@dataclass
class AdminProviderOpsGetBalanceAdapter(AdminApiAdapter):
provider_id: str
refresh: bool
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
return await _get_balance_response(self.provider_id, self.refresh, context.db)
@dataclass
class AdminProviderOpsRefreshBalanceAdapter(AdminApiAdapter):
provider_id: str
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
return await _refresh_balance_response(self.provider_id, context.db)
@dataclass
class AdminProviderOpsCheckinAdapter(AdminApiAdapter):
provider_id: str
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
return await _checkin_response(self.provider_id, context.db)
@dataclass
class AdminProviderOpsBatchBalanceAdapter(AdminApiAdapter):
provider_ids: list[str] | None
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
return await _batch_query_balance_response(self.provider_ids, context.db)