Some checks failed
continuous-integration/drone/push Build is failing
- Add Vue 3 frontend with Element Plus - Implement login, dashboard, tenant management - Add app configuration, logs viewer, stats pages - Add user management for admins - Update Drone CI to build and deploy frontend - Frontend ports: 3001 (test), 4001 (prod)
302 lines
8.4 KiB
Python
302 lines
8.4 KiB
Python
"""租户管理路由"""
|
||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||
from pydantic import BaseModel
|
||
from typing import Optional, List
|
||
from datetime import date
|
||
from sqlalchemy.orm import Session
|
||
from sqlalchemy import func
|
||
|
||
from ..database import get_db
|
||
from ..models.tenant import Tenant, Subscription
|
||
from ..models.stats import TenantUsageDaily
|
||
from .auth import get_current_user, require_operator
|
||
from ..models.user import User
|
||
|
||
router = APIRouter(prefix="/tenants", tags=["租户管理"])
|
||
|
||
|
||
# Schemas
|
||
|
||
class TenantCreate(BaseModel):
|
||
code: str
|
||
name: str
|
||
contact_info: Optional[dict] = None
|
||
status: str = "active"
|
||
expired_at: Optional[date] = None
|
||
|
||
|
||
class TenantUpdate(BaseModel):
|
||
name: Optional[str] = None
|
||
contact_info: Optional[dict] = None
|
||
status: Optional[str] = None
|
||
expired_at: Optional[date] = None
|
||
|
||
|
||
class SubscriptionCreate(BaseModel):
|
||
tenant_id: int
|
||
app_code: str
|
||
start_date: Optional[date] = None
|
||
end_date: Optional[date] = None
|
||
quota: Optional[dict] = None
|
||
status: str = "active"
|
||
|
||
|
||
class SubscriptionUpdate(BaseModel):
|
||
start_date: Optional[date] = None
|
||
end_date: Optional[date] = None
|
||
quota: Optional[dict] = None
|
||
status: Optional[str] = None
|
||
|
||
|
||
# API Endpoints
|
||
|
||
@router.get("")
|
||
async def list_tenants(
|
||
page: int = Query(1, ge=1),
|
||
size: int = Query(20, ge=1, le=100),
|
||
status: Optional[str] = None,
|
||
keyword: Optional[str] = None,
|
||
user: User = Depends(get_current_user),
|
||
db: Session = Depends(get_db)
|
||
):
|
||
"""获取租户列表"""
|
||
query = db.query(Tenant)
|
||
|
||
if status:
|
||
query = query.filter(Tenant.status == status)
|
||
if keyword:
|
||
query = query.filter(
|
||
(Tenant.code.contains(keyword)) | (Tenant.name.contains(keyword))
|
||
)
|
||
|
||
total = query.count()
|
||
tenants = query.order_by(Tenant.id.desc()).offset((page - 1) * size).limit(size).all()
|
||
|
||
return {
|
||
"total": total,
|
||
"page": page,
|
||
"size": size,
|
||
"items": [
|
||
{
|
||
"id": t.id,
|
||
"code": t.code,
|
||
"name": t.name,
|
||
"contact_info": t.contact_info,
|
||
"status": t.status,
|
||
"expired_at": t.expired_at,
|
||
"created_at": t.created_at
|
||
}
|
||
for t in tenants
|
||
]
|
||
}
|
||
|
||
|
||
@router.get("/{tenant_id}")
|
||
async def get_tenant(
|
||
tenant_id: int,
|
||
user: User = Depends(get_current_user),
|
||
db: Session = Depends(get_db)
|
||
):
|
||
"""获取租户详情"""
|
||
tenant = db.query(Tenant).filter(Tenant.id == tenant_id).first()
|
||
if not tenant:
|
||
raise HTTPException(status_code=404, detail="租户不存在")
|
||
|
||
# 获取订阅
|
||
subscriptions = db.query(Subscription).filter(
|
||
Subscription.tenant_id == tenant_id
|
||
).all()
|
||
|
||
# 获取用量统计(最近30天)
|
||
usage = db.query(
|
||
func.sum(TenantUsageDaily.ai_calls).label('total_calls'),
|
||
func.sum(TenantUsageDaily.ai_tokens).label('total_tokens'),
|
||
func.sum(TenantUsageDaily.ai_cost).label('total_cost')
|
||
).filter(
|
||
TenantUsageDaily.tenant_id == tenant_id
|
||
).first()
|
||
|
||
return {
|
||
"id": tenant.id,
|
||
"code": tenant.code,
|
||
"name": tenant.name,
|
||
"contact_info": tenant.contact_info,
|
||
"status": tenant.status,
|
||
"expired_at": tenant.expired_at,
|
||
"created_at": tenant.created_at,
|
||
"updated_at": tenant.updated_at,
|
||
"subscriptions": [
|
||
{
|
||
"id": s.id,
|
||
"app_code": s.app_code,
|
||
"start_date": s.start_date,
|
||
"end_date": s.end_date,
|
||
"quota": s.quota,
|
||
"status": s.status
|
||
}
|
||
for s in subscriptions
|
||
],
|
||
"usage_summary": {
|
||
"total_calls": int(usage.total_calls or 0),
|
||
"total_tokens": int(usage.total_tokens or 0),
|
||
"total_cost": float(usage.total_cost or 0)
|
||
}
|
||
}
|
||
|
||
|
||
@router.post("")
|
||
async def create_tenant(
|
||
data: TenantCreate,
|
||
user: User = Depends(require_operator),
|
||
db: Session = Depends(get_db)
|
||
):
|
||
"""创建租户"""
|
||
# 检查 code 是否重复
|
||
exists = db.query(Tenant).filter(Tenant.code == data.code).first()
|
||
if exists:
|
||
raise HTTPException(status_code=400, detail="租户代码已存在")
|
||
|
||
tenant = Tenant(
|
||
code=data.code,
|
||
name=data.name,
|
||
contact_info=data.contact_info,
|
||
status=data.status,
|
||
expired_at=data.expired_at
|
||
)
|
||
db.add(tenant)
|
||
db.commit()
|
||
db.refresh(tenant)
|
||
|
||
return {"success": True, "id": tenant.id}
|
||
|
||
|
||
@router.put("/{tenant_id}")
|
||
async def update_tenant(
|
||
tenant_id: int,
|
||
data: TenantUpdate,
|
||
user: User = Depends(require_operator),
|
||
db: Session = Depends(get_db)
|
||
):
|
||
"""更新租户"""
|
||
tenant = db.query(Tenant).filter(Tenant.id == tenant_id).first()
|
||
if not tenant:
|
||
raise HTTPException(status_code=404, detail="租户不存在")
|
||
|
||
update_data = data.model_dump(exclude_unset=True)
|
||
for key, value in update_data.items():
|
||
setattr(tenant, key, value)
|
||
|
||
db.commit()
|
||
return {"success": True}
|
||
|
||
|
||
@router.delete("/{tenant_id}")
|
||
async def delete_tenant(
|
||
tenant_id: int,
|
||
user: User = Depends(require_operator),
|
||
db: Session = Depends(get_db)
|
||
):
|
||
"""删除租户"""
|
||
tenant = db.query(Tenant).filter(Tenant.id == tenant_id).first()
|
||
if not tenant:
|
||
raise HTTPException(status_code=404, detail="租户不存在")
|
||
|
||
# 删除关联的订阅
|
||
db.query(Subscription).filter(Subscription.tenant_id == tenant_id).delete()
|
||
db.delete(tenant)
|
||
db.commit()
|
||
|
||
return {"success": True}
|
||
|
||
|
||
# 订阅管理
|
||
|
||
@router.get("/{tenant_id}/subscriptions")
|
||
async def list_subscriptions(
|
||
tenant_id: int,
|
||
user: User = Depends(get_current_user),
|
||
db: Session = Depends(get_db)
|
||
):
|
||
"""获取租户订阅列表"""
|
||
subscriptions = db.query(Subscription).filter(
|
||
Subscription.tenant_id == tenant_id
|
||
).all()
|
||
|
||
return [
|
||
{
|
||
"id": s.id,
|
||
"tenant_id": s.tenant_id,
|
||
"app_code": s.app_code,
|
||
"start_date": s.start_date,
|
||
"end_date": s.end_date,
|
||
"quota": s.quota,
|
||
"status": s.status,
|
||
"created_at": s.created_at
|
||
}
|
||
for s in subscriptions
|
||
]
|
||
|
||
|
||
@router.post("/{tenant_id}/subscriptions")
|
||
async def create_subscription(
|
||
tenant_id: int,
|
||
data: SubscriptionCreate,
|
||
user: User = Depends(require_operator),
|
||
db: Session = Depends(get_db)
|
||
):
|
||
"""创建订阅"""
|
||
tenant = db.query(Tenant).filter(Tenant.id == tenant_id).first()
|
||
if not tenant:
|
||
raise HTTPException(status_code=404, detail="租户不存在")
|
||
|
||
subscription = Subscription(
|
||
tenant_id=tenant_id,
|
||
app_code=data.app_code,
|
||
start_date=data.start_date,
|
||
end_date=data.end_date,
|
||
quota=data.quota,
|
||
status=data.status
|
||
)
|
||
db.add(subscription)
|
||
db.commit()
|
||
db.refresh(subscription)
|
||
|
||
return {"success": True, "id": subscription.id}
|
||
|
||
|
||
@router.put("/subscriptions/{subscription_id}")
|
||
async def update_subscription(
|
||
subscription_id: int,
|
||
data: SubscriptionUpdate,
|
||
user: User = Depends(require_operator),
|
||
db: Session = Depends(get_db)
|
||
):
|
||
"""更新订阅"""
|
||
subscription = db.query(Subscription).filter(Subscription.id == subscription_id).first()
|
||
if not subscription:
|
||
raise HTTPException(status_code=404, detail="订阅不存在")
|
||
|
||
update_data = data.model_dump(exclude_unset=True)
|
||
for key, value in update_data.items():
|
||
setattr(subscription, key, value)
|
||
|
||
db.commit()
|
||
return {"success": True}
|
||
|
||
|
||
@router.delete("/subscriptions/{subscription_id}")
|
||
async def delete_subscription(
|
||
subscription_id: int,
|
||
user: User = Depends(require_operator),
|
||
db: Session = Depends(get_db)
|
||
):
|
||
"""删除订阅"""
|
||
subscription = db.query(Subscription).filter(Subscription.id == subscription_id).first()
|
||
if not subscription:
|
||
raise HTTPException(status_code=404, detail="订阅不存在")
|
||
|
||
db.delete(subscription)
|
||
db.commit()
|
||
|
||
return {"success": True}
|