forked from xiaohei/taiji-AI-PAD
更新计费逻辑
This commit is contained in:
@@ -0,0 +1,595 @@
|
||||
"""
|
||||
超级管理员API路由
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy import select, func, and_, desc, or_
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
import uuid
|
||||
|
||||
from database import get_db
|
||||
from models import (
|
||||
User, Channel, Agent, ResourceAllocation,
|
||||
BillingRecord, Application, ModelProvider
|
||||
)
|
||||
from app.auth import require_auth, get_password_hash
|
||||
from app.schemas import (
|
||||
SuccessResponse,
|
||||
CreateChannelRequest,
|
||||
ChannelInfo,
|
||||
ChannelResourceAllocation,
|
||||
ApplicationInfo,
|
||||
ReviewApplicationRequest,
|
||||
AdminBillingResponse,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/api/admin", tags=["超级管理员"])
|
||||
|
||||
|
||||
def _verify_admin_permission(principal: dict):
|
||||
"""验证超级管理员权限"""
|
||||
role = principal.get("claims", {}).get("role")
|
||||
if role != "super_admin":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="需要超级管理员权限"
|
||||
)
|
||||
|
||||
|
||||
# ============= 概览 =============
|
||||
|
||||
@router.get("/dashboard/stats", response_model=SuccessResponse)
|
||||
async def get_admin_dashboard_stats(
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
获取平台全局统计
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
# 渠道总数
|
||||
channels_count = await db.execute(select(func.count(Channel.id)))
|
||||
total_channels = channels_count.scalar() or 0
|
||||
|
||||
# 租户总数
|
||||
tenants_count = await db.execute(
|
||||
select(func.count(User.id)).where(User.role == "user")
|
||||
)
|
||||
total_tenants = tenants_count.scalar() or 0
|
||||
|
||||
# Agent总数
|
||||
agents_count = await db.execute(select(func.count(Agent.id)))
|
||||
total_agents = agents_count.scalar() or 0
|
||||
|
||||
# 总调用次数
|
||||
calls_count = await db.execute(select(func.count(BillingRecord.id)))
|
||||
total_calls = calls_count.scalar() or 0
|
||||
|
||||
# 总收入
|
||||
revenue = await db.execute(select(func.sum(BillingRecord.cost)))
|
||||
total_revenue = float(revenue.scalar() or 0)
|
||||
|
||||
return SuccessResponse(
|
||||
data={
|
||||
"totalChannels": total_channels,
|
||||
"totalTenants": total_tenants,
|
||||
"totalAgents": total_agents,
|
||||
"totalCalls": total_calls,
|
||||
"totalRevenue": total_revenue,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
# ============= 渠道管理 =============
|
||||
|
||||
@router.get("/channels", response_model=SuccessResponse)
|
||||
async def list_channels(
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
获取所有渠道列表
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
result = await db.execute(select(Channel))
|
||||
channels = result.scalars().all()
|
||||
|
||||
data = [
|
||||
{
|
||||
"id": str(channel.id),
|
||||
"name": channel.name,
|
||||
"email": channel.email,
|
||||
"commissionRate": float(channel.commission_rate),
|
||||
"channelCredit": float(channel.channel_credit),
|
||||
"customAgentCpu": float(channel.custom_agent_cpu),
|
||||
"customAgentMemory": float(channel.custom_agent_memory),
|
||||
"status": channel.status,
|
||||
"createdAt": channel.created_at.isoformat(),
|
||||
}
|
||||
for channel in channels
|
||||
]
|
||||
|
||||
return SuccessResponse(data={"channels": data})
|
||||
|
||||
|
||||
@router.post("/channels/create", response_model=SuccessResponse)
|
||||
async def create_channel(
|
||||
req: CreateChannelRequest,
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
创建渠道
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
# 检查邮箱是否已存在
|
||||
result = await db.execute(
|
||||
select(Channel).where(Channel.email == req.email)
|
||||
)
|
||||
if result.scalar_one_or_none():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="邮箱已被使用"
|
||||
)
|
||||
|
||||
# 创建渠道
|
||||
password_hash = get_password_hash(req.password)
|
||||
channel = Channel(
|
||||
name=req.name,
|
||||
email=req.email,
|
||||
password_hash=password_hash,
|
||||
commission_rate=req.commissionRate,
|
||||
channel_credit=0,
|
||||
custom_agent_cpu=2,
|
||||
custom_agent_memory=4,
|
||||
status="active",
|
||||
)
|
||||
|
||||
db.add(channel)
|
||||
await db.commit()
|
||||
await db.refresh(channel)
|
||||
|
||||
return SuccessResponse(
|
||||
data={
|
||||
"id": str(channel.id),
|
||||
"name": channel.name,
|
||||
"email": channel.email,
|
||||
},
|
||||
message="渠道创建成功"
|
||||
)
|
||||
|
||||
|
||||
@router.put("/channels/{channel_id}/resources", response_model=SuccessResponse)
|
||||
async def allocate_channel_resources(
|
||||
channel_id: str,
|
||||
req: ChannelResourceAllocation,
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
统一管理渠道资源
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
# 验证渠道
|
||||
result = await db.execute(
|
||||
select(Channel).where(Channel.id == channel_id)
|
||||
)
|
||||
channel = result.scalar_one_or_none()
|
||||
|
||||
if not channel:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="渠道不存在"
|
||||
)
|
||||
|
||||
# 删除现有资源分配
|
||||
await db.execute(
|
||||
select(ResourceAllocation).where(
|
||||
and_(
|
||||
ResourceAllocation.target_id == channel_id,
|
||||
ResourceAllocation.target_type == "channel"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# 分配模型供应商资源
|
||||
for model_id in req.models:
|
||||
allocation = ResourceAllocation(
|
||||
target_id=channel_id,
|
||||
target_type="channel",
|
||||
resource_type="model",
|
||||
resource_id=model_id,
|
||||
)
|
||||
db.add(allocation)
|
||||
|
||||
# 分配Agent资源
|
||||
for agent_alloc in req.agents:
|
||||
allocation = ResourceAllocation(
|
||||
target_id=channel_id,
|
||||
target_type="channel",
|
||||
resource_type="agent",
|
||||
resource_id=agent_alloc.agentId,
|
||||
quantity=agent_alloc.quantity,
|
||||
)
|
||||
db.add(allocation)
|
||||
|
||||
# 更新自定义Agent资源和授信额度
|
||||
if req.customAgentResources:
|
||||
channel.custom_agent_cpu = req.customAgentResources.cpu
|
||||
channel.custom_agent_memory = req.customAgentResources.memory
|
||||
|
||||
channel.channel_credit = req.channelCredit
|
||||
|
||||
await db.commit()
|
||||
|
||||
return SuccessResponse(message="渠道资源分配成功")
|
||||
|
||||
|
||||
# ============= 申请审批 =============
|
||||
|
||||
@router.get("/channels/applications", response_model=SuccessResponse)
|
||||
async def list_applications(
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
获取所有渠道申请
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
result = await db.execute(
|
||||
select(Application, Channel)
|
||||
.join(Channel, Application.channel_id == Channel.id)
|
||||
.order_by(desc(Application.created_at))
|
||||
)
|
||||
|
||||
data = []
|
||||
for app, channel in result.all():
|
||||
details = {}
|
||||
if app.type == "model":
|
||||
details = {
|
||||
"modelName": app.model_name,
|
||||
"rpm": app.rpm,
|
||||
"tpm": app.tpm,
|
||||
}
|
||||
else:
|
||||
details = {
|
||||
"agentType": app.agent_type,
|
||||
"quantity": app.quantity,
|
||||
}
|
||||
|
||||
data.append({
|
||||
"id": str(app.id),
|
||||
"channelId": str(app.channel_id),
|
||||
"channelName": channel.name,
|
||||
"type": app.type,
|
||||
"details": details,
|
||||
"reason": app.reason or "",
|
||||
"status": app.status,
|
||||
"createdAt": app.created_at.isoformat(),
|
||||
})
|
||||
|
||||
return SuccessResponse(data={"data": data})
|
||||
|
||||
|
||||
@router.put("/channels/applications/{application_id}/review", response_model=SuccessResponse)
|
||||
async def review_application(
|
||||
application_id: str,
|
||||
req: ReviewApplicationRequest,
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
审批申请
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
user_id = principal.get("user_id")
|
||||
|
||||
# 查询申请
|
||||
result = await db.execute(
|
||||
select(Application).where(Application.id == application_id)
|
||||
)
|
||||
application = result.scalar_one_or_none()
|
||||
|
||||
if not application:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="申请不存在"
|
||||
)
|
||||
|
||||
if application.status != "pending":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="该申请已被处理"
|
||||
)
|
||||
|
||||
# 更新申请状态
|
||||
application.status = "approved" if req.approved else "rejected"
|
||||
application.reviewed_by = user_id
|
||||
application.review_reason = req.reason
|
||||
application.reviewed_at = datetime.utcnow()
|
||||
|
||||
# 如果批准,自动分配资源
|
||||
if req.approved:
|
||||
if application.type == "model":
|
||||
# 查找模型供应商
|
||||
result = await db.execute(
|
||||
select(ModelProvider).where(ModelProvider.name == application.model_name)
|
||||
)
|
||||
model_provider = result.scalar_one_or_none()
|
||||
|
||||
if model_provider:
|
||||
allocation = ResourceAllocation(
|
||||
target_id=str(application.channel_id),
|
||||
target_type="channel",
|
||||
resource_type="model",
|
||||
resource_id=str(model_provider.id),
|
||||
rpm=application.rpm,
|
||||
tpm=application.tpm,
|
||||
)
|
||||
db.add(allocation)
|
||||
|
||||
elif application.type == "agent":
|
||||
# 查找Agent(简化处理,实际应该根据agent_type查找)
|
||||
result = await db.execute(
|
||||
select(Agent).where(Agent.name == application.agent_type).limit(1)
|
||||
)
|
||||
agent = result.scalar_one_or_none()
|
||||
|
||||
if agent:
|
||||
allocation = ResourceAllocation(
|
||||
target_id=str(application.channel_id),
|
||||
target_type="channel",
|
||||
resource_type="agent",
|
||||
resource_id=str(agent.id),
|
||||
quantity=application.quantity,
|
||||
)
|
||||
db.add(allocation)
|
||||
|
||||
await db.commit()
|
||||
|
||||
return SuccessResponse(
|
||||
message=f"申请已{'批准' if req.approved else '拒绝'}"
|
||||
)
|
||||
|
||||
|
||||
# ============= 资源管理 =============
|
||||
|
||||
@router.get("/resources/models", response_model=SuccessResponse)
|
||||
async def list_model_providers(
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
获取所有模型供应商
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
result = await db.execute(select(ModelProvider))
|
||||
providers = result.scalars().all()
|
||||
|
||||
data = [
|
||||
{
|
||||
"id": str(provider.id),
|
||||
"name": provider.name,
|
||||
"provider": provider.provider,
|
||||
"apiUrl": provider.api_url,
|
||||
"supportedModels": provider.supported_models,
|
||||
"rpm": provider.rpm,
|
||||
"tpm": provider.tpm,
|
||||
"status": provider.status,
|
||||
"isActive": provider.is_active,
|
||||
}
|
||||
for provider in providers
|
||||
]
|
||||
|
||||
return SuccessResponse(data={"providers": data})
|
||||
|
||||
|
||||
@router.get("/resources/agents", response_model=SuccessResponse)
|
||||
async def list_all_agents(
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
获取所有Agent资源
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
result = await db.execute(select(Agent))
|
||||
agents = result.scalars().all()
|
||||
|
||||
data = [
|
||||
{
|
||||
"id": str(agent.id),
|
||||
"name": agent.name,
|
||||
"type": agent.type,
|
||||
"category": agent.category,
|
||||
"cpu": float(agent.cpu),
|
||||
"memory": float(agent.memory),
|
||||
"status": agent.status,
|
||||
}
|
||||
for agent in agents
|
||||
]
|
||||
|
||||
return SuccessResponse(data={"agents": data})
|
||||
|
||||
|
||||
# ============= 监控 =============
|
||||
|
||||
@router.get("/monitoring/agents", response_model=SuccessResponse)
|
||||
async def monitor_agents(
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
监控Agent健康状态和性能指标
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
result = await db.execute(
|
||||
select(Agent).where(Agent.type == "platform")
|
||||
)
|
||||
agents = result.scalars().all()
|
||||
|
||||
data = [
|
||||
{
|
||||
"id": str(agent.id),
|
||||
"name": agent.name,
|
||||
"status": agent.status,
|
||||
"totalExecutions": agent.total_executions,
|
||||
"successRate": float(agent.success_rate),
|
||||
"avgExecutionTime": float(agent.avg_execution_time),
|
||||
"cpu": float(agent.cpu),
|
||||
"memory": float(agent.memory),
|
||||
}
|
||||
for agent in agents
|
||||
]
|
||||
|
||||
return SuccessResponse(data={"agents": data})
|
||||
|
||||
|
||||
# ============= 计费(三维度) =============
|
||||
|
||||
@router.get("/billing/overview", response_model=SuccessResponse)
|
||||
async def get_billing_overview(
|
||||
startTime: str = Query(...),
|
||||
endTime: str = Query(...),
|
||||
channelName: Optional[str] = Query(None),
|
||||
tenantName: Optional[str] = Query(None),
|
||||
minCalls: Optional[int] = Query(None),
|
||||
maxCalls: Optional[int] = Query(None),
|
||||
export: Optional[str] = Query(None, pattern="^(excel|csv|pdf)$"),
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
获取三维度计费统计(渠道/租户/调用)
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
# 解析时间
|
||||
start_dt = datetime.fromisoformat(startTime.replace("Z", "+00:00"))
|
||||
end_dt = datetime.fromisoformat(endTime.replace("Z", "+00:00"))
|
||||
|
||||
# 渠道统计
|
||||
channel_stats_result = await db.execute(
|
||||
select(
|
||||
Channel.id,
|
||||
Channel.name,
|
||||
func.count(BillingRecord.id).label("calls"),
|
||||
func.sum(BillingRecord.eu).label("total_eu"),
|
||||
func.sum(BillingRecord.cost).label("total_cost"),
|
||||
)
|
||||
.select_from(BillingRecord)
|
||||
.join(Channel, BillingRecord.channel_id == Channel.id)
|
||||
.where(
|
||||
and_(
|
||||
BillingRecord.timestamp >= start_dt,
|
||||
BillingRecord.timestamp <= end_dt,
|
||||
)
|
||||
)
|
||||
.group_by(Channel.id, Channel.name)
|
||||
)
|
||||
|
||||
channel_stats = [
|
||||
{
|
||||
"channelId": str(row.id),
|
||||
"channelName": row.name,
|
||||
"calls": row.calls,
|
||||
"totalEU": float(row.total_eu or 0),
|
||||
"totalCost": float(row.total_cost or 0),
|
||||
}
|
||||
for row in channel_stats_result.all()
|
||||
]
|
||||
|
||||
# 租户统计
|
||||
tenant_stats_result = await db.execute(
|
||||
select(
|
||||
User.id,
|
||||
User.name,
|
||||
Channel.name.label("channel_name"),
|
||||
func.count(BillingRecord.id).label("calls"),
|
||||
func.sum(BillingRecord.eu).label("total_eu"),
|
||||
func.sum(BillingRecord.cost).label("total_cost"),
|
||||
)
|
||||
.select_from(BillingRecord)
|
||||
.join(User, BillingRecord.tenant_id == User.id)
|
||||
.join(Channel, User.channel_id == Channel.id)
|
||||
.where(
|
||||
and_(
|
||||
BillingRecord.timestamp >= start_dt,
|
||||
BillingRecord.timestamp <= end_dt,
|
||||
)
|
||||
)
|
||||
.group_by(User.id, User.name, Channel.name)
|
||||
)
|
||||
|
||||
tenant_stats = [
|
||||
{
|
||||
"tenantId": str(row.id),
|
||||
"tenantName": row.name,
|
||||
"channelName": row.channel_name,
|
||||
"calls": row.calls,
|
||||
"totalEU": float(row.total_eu or 0),
|
||||
"totalCost": float(row.total_cost or 0),
|
||||
}
|
||||
for row in tenant_stats_result.all()
|
||||
]
|
||||
|
||||
# 调用记录
|
||||
records_result = await db.execute(
|
||||
select(BillingRecord, Channel.name, User.name)
|
||||
.join(Channel, BillingRecord.channel_id == Channel.id)
|
||||
.join(User, BillingRecord.tenant_id == User.id)
|
||||
.where(
|
||||
and_(
|
||||
BillingRecord.timestamp >= start_dt,
|
||||
BillingRecord.timestamp <= end_dt,
|
||||
)
|
||||
)
|
||||
.order_by(desc(BillingRecord.timestamp))
|
||||
.limit(100)
|
||||
)
|
||||
|
||||
call_records = [
|
||||
{
|
||||
"id": str(record.id),
|
||||
"timestamp": record.timestamp.isoformat(),
|
||||
"channelName": channel_name,
|
||||
"tenantName": tenant_name,
|
||||
"agentName": record.agent_name,
|
||||
"duration": record.duration,
|
||||
"eu": record.eu,
|
||||
"cost": float(record.cost),
|
||||
}
|
||||
for record, channel_name, tenant_name in records_result.all()
|
||||
]
|
||||
|
||||
# 如果是导出请求
|
||||
if export:
|
||||
file_url = f"https://exports.taiji-ai.com/admin/{export}/billing_{datetime.utcnow().strftime('%Y%m%d%H%M%S')}.{export}"
|
||||
expires_at = (datetime.utcnow() + timedelta(hours=24)).isoformat()
|
||||
|
||||
return SuccessResponse(
|
||||
data={
|
||||
"fileUrl": file_url,
|
||||
"format": export,
|
||||
"expiresAt": expires_at,
|
||||
}
|
||||
)
|
||||
|
||||
return SuccessResponse(
|
||||
data={
|
||||
"channelStats": channel_stats,
|
||||
"tenantStats": tenant_stats,
|
||||
"callRecords": call_records,
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user