更新计费逻辑

This commit is contained in:
Ubuntu
2025-12-25 04:20:42 +00:00
parent e153d27f28
commit 2e09716dd9
31 changed files with 8682 additions and 367 deletions
+595
View File
@@ -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,
}
)