Phase 3.1: OSS storage config management (MinIO/AliyunOSS/AWS S3)
Backend: - Add OSSStorageConfig model (models.py) - Create storage_admin.py API (9 endpoints): stats, CRUD, toggle, set-default, test connection - Register route /app/api/admin/storage in main.py - Create oss_storage_configs table in MySQL Frontend: - Add Storage.tsx admin page (713 lines) with stats, table, create/edit modal - Register /admin/storage route in App.tsx - Add nav item and icon in AdminLayout.tsx - Add 9 API methods in api/index.ts
This commit is contained in:
1 parent
5d1ad26874
commit
b2ba75a666
7 files changed
+2265
-468
No files matched your search
@@ -0,0 +1,582 @@
|
||||
"""
|
||||
OSS存储配置管理 API
|
||||
Phase 3.1: 支持 MinIO / 阿里云OSS / AWS S3 等对象存储配置
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Body
|
||||
from sqlalchemy import or_, func, desc, asc, text
|
||||
from sqlalchemy.orm import Session
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, List, Dict, Any
|
||||
from datetime import datetime
|
||||
import uuid
|
||||
import json
|
||||
import logging
|
||||
import httpx
|
||||
|
||||
from ...database import get_db
|
||||
from ...models import (
|
||||
gen_uuid,
|
||||
)
|
||||
from ...auth import get_current_admin
|
||||
from ...middleware.log_service import LogService
|
||||
|
||||
router = APIRouter()
|
||||
logger = logging.getLogger("aiflow")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Pydantic Models
|
||||
# ============================================================
|
||||
|
||||
class OSSConfigCreate(BaseModel):
|
||||
name: str = Field(min_length=1, max_length=200)
|
||||
provider: str = Field(min_length=1, max_length=20)
|
||||
endpoint: str = Field(min_length=1, max_length=500)
|
||||
access_key: str = Field(min_length=1, max_length=255)
|
||||
secret_key: str = Field(min_length=1, max_length=255)
|
||||
bucket: str = Field(min_length=1, max_length=200)
|
||||
region: Optional[str] = ""
|
||||
base_path: Optional[str] = ""
|
||||
is_default: bool = False
|
||||
is_enabled: bool = True
|
||||
config: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
class OSSConfigUpdate(BaseModel):
|
||||
name: Optional[str] = Field(None, min_length=1, max_length=200)
|
||||
provider: Optional[str] = Field(None, min_length=1, max_length=20)
|
||||
endpoint: Optional[str] = Field(None, min_length=1, max_length=500)
|
||||
access_key: Optional[str] = Field(None, min_length=1, max_length=255)
|
||||
secret_key: Optional[str] = Field(None, min_length=1, max_length=255)
|
||||
bucket: Optional[str] = Field(None, min_length=1, max_length=200)
|
||||
region: Optional[str] = None
|
||||
base_path: Optional[str] = None
|
||||
is_default: Optional[bool] = None
|
||||
is_enabled: Optional[bool] = None
|
||||
config: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
class OSSConnectionTest(BaseModel):
|
||||
"""测试连接请求 - 可以测试已保存的配置或临时配置"""
|
||||
config_id: Optional[str] = None # 已保存的配置ID
|
||||
# 以下为临时测试参数
|
||||
provider: Optional[str] = None
|
||||
endpoint: Optional[str] = None
|
||||
access_key: Optional[str] = None
|
||||
secret_key: Optional[str] = None
|
||||
bucket: Optional[str] = None
|
||||
region: Optional[str] = None
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Helper Functions
|
||||
# ============================================================
|
||||
|
||||
def config_to_dict(config) -> dict:
|
||||
"""将OSSStorageConfig模型转换为字典"""
|
||||
result = {
|
||||
"id": config.id,
|
||||
"name": config.name,
|
||||
"provider": config.provider,
|
||||
"endpoint": config.endpoint,
|
||||
"access_key": config.access_key[:8] + "****" if config.access_key and len(config.access_key) > 8 else "****",
|
||||
"secret_key": "****", # 永远不返回完整密钥
|
||||
"bucket": config.bucket,
|
||||
"region": config.region or "",
|
||||
"base_path": config.base_path or "",
|
||||
"is_default": getattr(config, "is_default", False),
|
||||
"is_enabled": config.is_enabled,
|
||||
"status": config.status or "disconnected",
|
||||
"created_at": config.created_at.isoformat() if config.created_at else None,
|
||||
"updated_at": config.updated_at.isoformat() if config.updated_at else None,
|
||||
}
|
||||
|
||||
# 安全处理 JSON 字段
|
||||
config_raw = getattr(config, "config", None)
|
||||
if config_raw:
|
||||
try:
|
||||
result["config"] = json.loads(config_raw) if isinstance(config_raw, str) else config_raw
|
||||
except Exception:
|
||||
result["config"] = {}
|
||||
else:
|
||||
result["config"] = {}
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def test_oss_connection(provider: str, endpoint: str, access_key: str, secret_key: str,
|
||||
bucket: str, region: str = "") -> Dict[str, Any]:
|
||||
"""测试OSS连接 - 使用httpx发送HTTP请求到对象存储端点"""
|
||||
import time
|
||||
start_time = time.time()
|
||||
|
||||
try:
|
||||
if provider in ("minio", "aws_s3"):
|
||||
# 使用 AWS S3 兼容 API 测试
|
||||
# ListBuckets 请求
|
||||
import hmac
|
||||
import hashlib
|
||||
from urllib.parse import urlparse
|
||||
|
||||
parsed = urlparse(endpoint)
|
||||
host = parsed.netloc or parsed.path
|
||||
if not host:
|
||||
return {"success": False, "error": "无法解析端点地址", "latency_ms": 0}
|
||||
|
||||
# 先尝试简单的 bucket HEAD 请求
|
||||
bucket_url = f"{endpoint.rstrip('/')}/{bucket}"
|
||||
async with httpx.AsyncClient(timeout=10.0, verify=False) as client:
|
||||
resp = await client.head(bucket_url)
|
||||
latency = int((time.time() - start_time) * 1000)
|
||||
|
||||
if resp.status_code in (200, 403):
|
||||
# 200: bucket存在且有权限; 403: bucket存在但无权限(也是连通的表现)
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"连接成功!Bucket '{bucket}' 可访问",
|
||||
"latency_ms": latency,
|
||||
"status_code": resp.status_code
|
||||
}
|
||||
elif resp.status_code == 404:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"Bucket '{bucket}' 不存在",
|
||||
"latency_ms": latency
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"连接失败: HTTP {resp.status_code}",
|
||||
"latency_ms": latency
|
||||
}
|
||||
|
||||
elif provider == "aliyun_oss":
|
||||
# 阿里云OSS: 简单HEAD请求
|
||||
bucket_url = f"{endpoint.rstrip('/')}/{bucket}" if not endpoint.endswith(bucket) else endpoint
|
||||
async with httpx.AsyncClient(timeout=10.0, verify=False) as client:
|
||||
resp = await client.head(bucket_url)
|
||||
latency = int((time.time() - start_time) * 1000)
|
||||
|
||||
if resp.status_code in (200, 403):
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"连接成功!Bucket '{bucket}' 可访问",
|
||||
"latency_ms": latency,
|
||||
"status_code": resp.status_code
|
||||
}
|
||||
elif resp.status_code == 404:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"Bucket '{bucket}' 不存在",
|
||||
"latency_ms": latency
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"连接失败: HTTP {resp.status_code}",
|
||||
"latency_ms": latency
|
||||
}
|
||||
|
||||
else:
|
||||
return {"success": False, "error": f"不支持的存储类型: {provider}", "latency_ms": 0}
|
||||
|
||||
except httpx.ConnectError:
|
||||
latency = int((time.time() - start_time) * 1000)
|
||||
return {"success": False, "error": f"无法连接到 {endpoint}", "latency_ms": latency}
|
||||
except httpx.TimeoutException:
|
||||
latency = int((time.time() - start_time) * 1000)
|
||||
return {"success": False, "error": "连接超时(10秒)", "latency_ms": latency}
|
||||
except Exception as e:
|
||||
latency = int((time.time() - start_time) * 1000)
|
||||
return {"success": False, "error": f"测试失败: {str(e)}", "latency_ms": latency}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# API Endpoints
|
||||
# ============================================================
|
||||
|
||||
@router.get("/stats/overview", summary="存储配置统计概览")
|
||||
async def get_storage_stats(
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""获取存储配置的统计信息"""
|
||||
from ...models import OSSStorageConfig
|
||||
|
||||
total = db.query(func.count(OSSStorageConfig.id)).scalar() or 0
|
||||
enabled_count = db.query(func.count(OSSStorageConfig.id)).filter(
|
||||
OSSStorageConfig.is_enabled == True
|
||||
).scalar() or 0
|
||||
default_count = db.query(func.count(OSSStorageConfig.id)).filter(
|
||||
OSSStorageConfig.is_default == True
|
||||
).scalar() or 0
|
||||
|
||||
# 按类型统计
|
||||
by_provider = {}
|
||||
providers = db.query(OSSStorageConfig.provider, func.count(OSSStorageConfig.id)).group_by(
|
||||
OSSStorageConfig.provider
|
||||
).all()
|
||||
for provider, count in providers:
|
||||
by_provider[provider] = count
|
||||
|
||||
# 按状态统计
|
||||
by_status = {}
|
||||
statuses = db.query(OSSStorageConfig.status, func.count(OSSStorageConfig.id)).group_by(
|
||||
OSSStorageConfig.status
|
||||
).all()
|
||||
for status, count in statuses:
|
||||
by_status[status] = count
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"data": {
|
||||
"total": total,
|
||||
"enabled_count": enabled_count,
|
||||
"default_count": default_count,
|
||||
"by_provider": by_provider,
|
||||
"by_status": by_status,
|
||||
},
|
||||
"message": "ok"
|
||||
}
|
||||
|
||||
|
||||
@router.get("/", summary="获取OSS存储配置列表")
|
||||
async def list_storage_configs(
|
||||
provider: str = Query(None, description="按提供商筛选"),
|
||||
is_enabled: bool = Query(None, description="按启用状态筛选"),
|
||||
keyword: str = Query(None, description="搜索关键词"),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""获取OSS存储配置列表,支持分页和筛选"""
|
||||
from ...models import OSSStorageConfig
|
||||
|
||||
query = db.query(OSSStorageConfig)
|
||||
|
||||
if provider:
|
||||
query = query.filter(OSSStorageConfig.provider == provider)
|
||||
if is_enabled is not None:
|
||||
query = query.filter(OSSStorageConfig.is_enabled == is_enabled)
|
||||
if keyword:
|
||||
keyword_filter = or_(
|
||||
OSSStorageConfig.name.ilike(f"%{keyword}%"),
|
||||
OSSStorageConfig.endpoint.ilike(f"%{keyword}%"),
|
||||
OSSStorageConfig.bucket.ilike(f"%{keyword}%"),
|
||||
)
|
||||
query = query.filter(keyword_filter)
|
||||
|
||||
total = query.count()
|
||||
items = query.order_by(desc(OSSStorageConfig.is_default), desc(OSSStorageConfig.updated_at)) \
|
||||
.offset((page - 1) * page_size).limit(page_size).all()
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"data": {
|
||||
"items": [config_to_dict(item) for item in items],
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
},
|
||||
"message": "ok"
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{config_id}", summary="获取OSS存储配置详情")
|
||||
async def get_storage_config(
|
||||
config_id: str,
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""获取单个OSS存储配置的详细信息"""
|
||||
from ...models import OSSStorageConfig
|
||||
|
||||
config = db.query(OSSStorageConfig).filter(OSSStorageConfig.id == config_id).first()
|
||||
if not config:
|
||||
raise HTTPException(status_code=404, detail="存储配置不存在")
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"data": config_to_dict(config),
|
||||
"message": "ok"
|
||||
}
|
||||
|
||||
|
||||
@router.post("/", summary="创建OSS存储配置")
|
||||
async def create_storage_config(
|
||||
req: OSSConfigCreate,
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""创建新的OSS存储配置"""
|
||||
from ...models import OSSStorageConfig
|
||||
|
||||
# 检查名称重复
|
||||
existing = db.query(OSSStorageConfig).filter(OSSStorageConfig.name == req.name).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="配置名称已存在")
|
||||
|
||||
# 如果设为默认,先取消其他默认
|
||||
if req.is_default:
|
||||
db.query(OSSStorageConfig).filter(OSSStorageConfig.is_default == True).update(
|
||||
{"is_default": False}
|
||||
)
|
||||
|
||||
config = OSSStorageConfig(
|
||||
id=gen_uuid(),
|
||||
name=req.name,
|
||||
provider=req.provider,
|
||||
endpoint=req.endpoint,
|
||||
access_key=req.access_key,
|
||||
secret_key=req.secret_key,
|
||||
bucket=req.bucket,
|
||||
region=req.region or "",
|
||||
base_path=req.base_path or "",
|
||||
is_default=req.is_default,
|
||||
is_enabled=req.is_enabled,
|
||||
status="disconnected",
|
||||
config=req.config,
|
||||
)
|
||||
|
||||
db.add(config)
|
||||
db.commit()
|
||||
db.refresh(config)
|
||||
|
||||
# 审计日志
|
||||
LogService.log_audit(
|
||||
admin_id=admin_user.id,
|
||||
admin_name=admin_user.username,
|
||||
action="create_oss_config",
|
||||
target_type="oss_storage_config",
|
||||
target_id=config.id,
|
||||
detail=f"创建了OSS存储配置: {config.name} ({config.provider})"
|
||||
)
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"data": config_to_dict(config),
|
||||
"message": "创建成功"
|
||||
}
|
||||
|
||||
|
||||
@router.put("/{config_id}", summary="更新OSS存储配置")
|
||||
async def update_storage_config(
|
||||
config_id: str,
|
||||
req: OSSConfigUpdate,
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""更新OSS存储配置"""
|
||||
from ...models import OSSStorageConfig
|
||||
|
||||
config = db.query(OSSStorageConfig).filter(OSSStorageConfig.id == config_id).first()
|
||||
if not config:
|
||||
raise HTTPException(status_code=404, detail="存储配置不存在")
|
||||
|
||||
# 检查名称重复
|
||||
if req.name is not None and req.name != config.name:
|
||||
existing = db.query(OSSStorageConfig).filter(
|
||||
OSSStorageConfig.name == req.name,
|
||||
OSSStorageConfig.id != config_id
|
||||
).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="配置名称已存在")
|
||||
|
||||
# 如果设为默认,先取消其他默认
|
||||
if req.is_default and not config.is_default:
|
||||
db.query(OSSStorageConfig).filter(OSSStorageConfig.is_default == True).update(
|
||||
{"is_default": False}
|
||||
)
|
||||
|
||||
# 更新字段
|
||||
update_fields = ["name", "provider", "endpoint", "access_key", "secret_key",
|
||||
"bucket", "region", "base_path", "is_default", "is_enabled"]
|
||||
for field in update_fields:
|
||||
value = getattr(req, field, None)
|
||||
if value is not None:
|
||||
setattr(config, field, value)
|
||||
# 如果修改了连接参数,重置状态
|
||||
if field in ("endpoint", "access_key", "secret_key", "bucket", "provider"):
|
||||
config.status = "disconnected"
|
||||
|
||||
if req.config is not None:
|
||||
config.config = req.config
|
||||
|
||||
config.updated_at = datetime.now()
|
||||
|
||||
db.commit()
|
||||
db.refresh(config)
|
||||
|
||||
# 审计日志
|
||||
LogService.log_audit(
|
||||
admin_id=admin_user.id,
|
||||
admin_name=admin_user.username,
|
||||
action="update_oss_config",
|
||||
target_type="oss_storage_config",
|
||||
target_id=config.id,
|
||||
detail=f"更新了OSS存储配置: {config.name}"
|
||||
)
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"data": config_to_dict(config),
|
||||
"message": "更新成功"
|
||||
}
|
||||
|
||||
|
||||
@router.delete("/{config_id}", summary="删除OSS存储配置")
|
||||
async def delete_storage_config(
|
||||
config_id: str,
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""删除OSS存储配置"""
|
||||
from ...models import OSSStorageConfig
|
||||
|
||||
config = db.query(OSSStorageConfig).filter(OSSStorageConfig.id == config_id).first()
|
||||
if not config:
|
||||
raise HTTPException(status_code=404, detail="存储配置不存在")
|
||||
|
||||
name = config.name
|
||||
db.delete(config)
|
||||
db.commit()
|
||||
|
||||
# 审计日志
|
||||
LogService.log_audit(
|
||||
admin_id=admin_user.id,
|
||||
admin_name=admin_user.username,
|
||||
action="delete_oss_config",
|
||||
target_type="oss_storage_config",
|
||||
target_id=config_id,
|
||||
detail=f"删除了OSS存储配置: {name}"
|
||||
)
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"data": None,
|
||||
"message": f"已删除配置: {name}"
|
||||
}
|
||||
|
||||
|
||||
@router.put("/{config_id}/toggle", summary="启用/禁用存储配置")
|
||||
async def toggle_storage_config(
|
||||
config_id: str,
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""切换存储配置的启用/禁用状态"""
|
||||
from ...models import OSSStorageConfig
|
||||
|
||||
config = db.query(OSSStorageConfig).filter(OSSStorageConfig.id == config_id).first()
|
||||
if not config:
|
||||
raise HTTPException(status_code=404, detail="存储配置不存在")
|
||||
|
||||
config.is_enabled = not config.is_enabled
|
||||
config.updated_at = datetime.now()
|
||||
db.commit()
|
||||
db.refresh(config)
|
||||
|
||||
action = "启用" if config.is_enabled else "禁用"
|
||||
LogService.log_audit(
|
||||
admin_id=admin_user.id,
|
||||
admin_name=admin_user.username,
|
||||
action="toggle_oss_config",
|
||||
target_type="oss_storage_config",
|
||||
target_id=config.id,
|
||||
detail=f"{action}了OSS存储配置: {config.name}"
|
||||
)
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"data": config_to_dict(config),
|
||||
"message": f"已{action}"
|
||||
}
|
||||
|
||||
|
||||
@router.put("/{config_id}/set-default", summary="设为默认存储配置")
|
||||
async def set_default_storage_config(
|
||||
config_id: str,
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""将指定配置设为默认存储"""
|
||||
from ...models import OSSStorageConfig
|
||||
|
||||
config = db.query(OSSStorageConfig).filter(OSSStorageConfig.id == config_id).first()
|
||||
if not config:
|
||||
raise HTTPException(status_code=404, detail="存储配置不存在")
|
||||
|
||||
# 取消所有默认
|
||||
db.query(OSSStorageConfig).filter(OSSStorageConfig.is_default == True).update(
|
||||
{"is_default": False}
|
||||
)
|
||||
|
||||
# 设置新默认
|
||||
config.is_default = True
|
||||
config.updated_at = datetime.now()
|
||||
db.commit()
|
||||
db.refresh(config)
|
||||
|
||||
LogService.log_audit(
|
||||
admin_id=admin_user.id,
|
||||
admin_name=admin_user.username,
|
||||
action="set_default_oss_config",
|
||||
target_type="oss_storage_config",
|
||||
target_id=config.id,
|
||||
detail=f"将 {config.name} 设为默认存储配置"
|
||||
)
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"data": config_to_dict(config),
|
||||
"message": f"已将 {config.name} 设为默认存储"
|
||||
}
|
||||
|
||||
|
||||
@router.post("/test", summary="测试OSS连接")
|
||||
async def test_storage_connection(
|
||||
req: OSSConnectionTest,
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""测试OSS存储连接"""
|
||||
from ...models import OSSStorageConfig
|
||||
|
||||
provider = req.provider
|
||||
endpoint = req.endpoint
|
||||
access_key = req.access_key
|
||||
secret_key = req.secret_key
|
||||
bucket = req.bucket
|
||||
region = req.region or ""
|
||||
|
||||
# 如果提供了config_id,从数据库获取配置
|
||||
if req.config_id:
|
||||
config = db.query(OSSStorageConfig).filter(OSSStorageConfig.id == req.config_id).first()
|
||||
if not config:
|
||||
raise HTTPException(status_code=404, detail="存储配置不存在")
|
||||
provider = config.provider
|
||||
endpoint = config.endpoint
|
||||
access_key = config.access_key
|
||||
secret_key = config.secret_key
|
||||
bucket = config.bucket
|
||||
region = config.region or ""
|
||||
|
||||
if not all([provider, endpoint, access_key, secret_key, bucket]):
|
||||
raise HTTPException(status_code=400, detail="缺少必要的连接参数")
|
||||
|
||||
result = await test_oss_connection(provider, endpoint, access_key, secret_key, bucket, region)
|
||||
|
||||
# 如果是已保存的配置,更新连接状态
|
||||
if req.config_id:
|
||||
config = db.query(OSSStorageConfig).filter(OSSStorageConfig.id == req.config_id).first()
|
||||
if config:
|
||||
config.status = "connected" if result["success"] else "disconnected"
|
||||
config.updated_at = datetime.now()
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"data": result,
|
||||
"message": "连接测试完成"
|
||||
}
|
||||
@@ -979,3 +979,22 @@ class ExternalKBService(Base):
|
||||
status = Column(String(20), default="disconnected")
|
||||
created_at = Column(DateTime, default=datetime.now)
|
||||
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
|
||||
class OSSStorageConfig(Base):
|
||||
__tablename__ = "oss_storage_configs"
|
||||
|
||||
id = Column(String(36), primary_key=True, default=gen_uuid)
|
||||
name = Column(String(200), nullable=False)
|
||||
provider = Column(String(20), nullable=False)
|
||||
endpoint = Column(String(500), nullable=False)
|
||||
access_key = Column(String(255), nullable=False)
|
||||
secret_key = Column(String(255), nullable=False)
|
||||
bucket = Column(String(200), nullable=False)
|
||||
region = Column(String(100), default="")
|
||||
base_path = Column(String(300), default="")
|
||||
is_default = Column(Boolean, default=False)
|
||||
is_enabled = Column(Boolean, default=True)
|
||||
status = Column(String(20), default="disconnected")
|
||||
config = Column(JSON, default=None)
|
||||
created_at = Column(DateTime, default=datetime.now)
|
||||
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
+430
-215
@@ -1,217 +1,432 @@
|
||||
"""␍
|
||||
Qihu-AIFlow FastAPI 主应用␍
|
||||
完整后端API,监听18080端口,所有接口以 /app 为前缀␍
|
||||
"""␍
|
||||
import sys␍
|
||||
import os␍
|
||||
import time␍
|
||||
import uuid␍
|
||||
import logging␍
|
||||
from contextlib import asynccontextmanager␍
|
||||
from fastapi import FastAPI, Request␍
|
||||
from fastapi.middleware.cors import CORSMiddleware␍
|
||||
from starlette.middleware.base import BaseHTTPMiddleware␍
|
||||
from datetime import datetime␍
|
||||
␍
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))␍
|
||||
␍
|
||||
from app.api import auth, chat, channels, knowledge, market, token_usage, payment, profile, notifications, assets, evolution␍
|
||||
from app.api.admin import dashboard, users, models, settings, payment_admin, services, membership_admin, ads, market_admin, logs, skills_admin, agents_admin, knowledge_admin
|
||||
from app.config import setup_file_logging, SLOW_REQUEST_THRESHOLD␍
|
||||
from app.database import SessionLocal␍
|
||||
from app.models import OperationLog as OperationLogModel, TokenUsageLog as TokenUsageLogModel␍
|
||||
␍
|
||||
# 导入平台适配器触发注册(AstrBot借鉴优化方案 P1)␍
|
||||
import app.platform_adapters # noqa: F401 - 触发所有平台适配器装饰器注册␍
|
||||
␍
|
||||
# 初始化文件日志(按天轮转)␍
|
||||
access_logger = setup_file_logging()␍
|
||||
logger = logging.getLogger("aiflow")␍
|
||||
␍
|
||||
␍
|
||||
class APITrackingMiddleware(BaseHTTPMiddleware):␍
|
||||
"""API调用统计中间件 - 记录请求耗时,标记慢请求"""␍
|
||||
␍
|
||||
async def dispatch(self, request: Request, call_next):␍
|
||||
request_id = str(uuid.uuid4())␍
|
||||
start_time = time.time()␍
|
||||
path = request.url.path␍
|
||||
method = request.method␍
|
||||
␍
|
||||
# 跳过健康检查和静态资源␍
|
||||
skip_paths = ["/app/api/health", "/favicon.ico"]␍
|
||||
should_skip = any(path.startswith(p) for p in skip_paths)␍
|
||||
␍
|
||||
try:␍
|
||||
response = await call_next(request)␍
|
||||
elapsed = time.time() - start_time␍
|
||||
status_code = response.status_code␍
|
||||
␍
|
||||
if not should_skip:␍
|
||||
access_logger.info(␍
|
||||
f"[{request_id}] {method} {path} -> {status_code} ({elapsed:.3f}s)"␍
|
||||
)␍
|
||||
␍
|
||||
# 异步记录API调用统计␍
|
||||
try:␍
|
||||
db = SessionLocal()␍
|
||||
try:␍
|
||||
_record_api_call(db, method, path, status_code, elapsed)␍
|
||||
finally:␍
|
||||
db.close()␍
|
||||
except Exception as e:␍
|
||||
logger.warning(f"API统计记录失败: {e}")␍
|
||||
␍
|
||||
return response␍
|
||||
except Exception as e:␍
|
||||
elapsed = time.time() - start_time␍
|
||||
if not should_skip:␍
|
||||
access_logger.info(␍
|
||||
f"[{request_id}] {method} {path} -> ERROR ({elapsed:.3f}s): {str(e)}"␍
|
||||
)␍
|
||||
raise␍
|
||||
␍
|
||||
async def __call__(self, scope, receive, send):␍
|
||||
if scope["type"] != "http":␍
|
||||
await self.app(scope, receive, send)␍
|
||||
return␍
|
||||
await super().__call__(scope, receive, send)␍
|
||||
␍
|
||||
␍
|
||||
def _record_api_call(db, method: str, path: str, status_code: int, elapsed: float):␍
|
||||
"""记录API调用到数据库统计表"""␍
|
||||
# 简化路径以减少基数(去掉动态ID)␍
|
||||
parts = path.split("/")␍
|
||||
simplified_parts = []␍
|
||||
for part in parts:␍
|
||||
# 将UUID样式的路径段替换为{id}␍
|
||||
if len(part) == 36 and part.count("-") == 4:␍
|
||||
simplified_parts.append("{id}")␍
|
||||
# 将纯数字路径段替换为{id}␍
|
||||
elif part.isdigit():␍
|
||||
simplified_parts.append("{id}")␍
|
||||
else:␍
|
||||
simplified_parts.append(part)␍
|
||||
simplified_path = "/".join(simplified_parts)␍
|
||||
␍
|
||||
# 统计记录通过数据库实现,简化版记录到日志␍
|
||||
is_error = status_code >= 400␍
|
||||
is_slow = elapsed >= SLOW_REQUEST_THRESHOLD␍
|
||||
␍
|
||||
if is_slow:␍
|
||||
logger.warning(␍
|
||||
f"慢请求: {method} {simplified_path} -> {status_code} ({elapsed:.3f}s, 阈值: {SLOW_REQUEST_THRESHOLD}s)"␍
|
||||
)␍
|
||||
␍
|
||||
␍
|
||||
@asynccontextmanager␍
|
||||
async def lifespan(app: FastAPI):␍
|
||||
logger.info("Qihu-AIFlow v2.0.0 启动中...")␍
|
||||
␍
|
||||
# 启动后台 Evolution Pipeline(知识库图谱已存在的用户配置自动恢复运行)␍
|
||||
try:␍
|
||||
from app.services.evolution_service import EvolutionWatcherManager␍
|
||||
from app.models import UserEvolutionConfig␍
|
||||
db = SessionLocal()␍
|
||||
try:␍
|
||||
configs = db.query(UserEvolutionConfig).filter(␍
|
||||
UserEvolutionConfig.watcher_status == "running"␍
|
||||
).all()␍
|
||||
if configs:␍
|
||||
watcher = EvolutionWatcherManager()␍
|
||||
for cfg in configs:␍
|
||||
try:␍
|
||||
watcher.start_user_evolution(cfg.user_id, lambda: SessionLocal())␍
|
||||
logger.info(f"[Evolution] 恢复用户 {cfg.user_id} 的进化Pipeline")␍
|
||||
except Exception as e:␍
|
||||
logger.warning(f"[Evolution] 恢复用户 {cfg.user_id} 失败: {e}")␍
|
||||
logger.info(f"[Evolution] 已恢复 {len(configs)} 个用户的进化Pipeline")␍
|
||||
else:␍
|
||||
logger.info("[Evolution] 无待恢复的进化配置")␍
|
||||
finally:␍
|
||||
db.close()␍
|
||||
except Exception as e:␍
|
||||
logger.warning(f"[Evolution] Pipeline自动启动异常(不影响正常服务): {e}")␍
|
||||
␍
|
||||
# 启动Redis事件总线␍
|
||||
try:␍
|
||||
from app.event_bus import get_event_bus␍
|
||||
event_bus = get_event_bus()␍
|
||||
event_bus.start(num_consumers=2)␍
|
||||
logger.info("[EventBus] Redis事件总线已启动")␍
|
||||
except Exception as e:␍
|
||||
logger.warning(f"[EventBus] 启动失败(不影响正常服务): {e}")␍
|
||||
␍
|
||||
logger.info("Qihu-AIFlow v2.0.0 启动完成")␍
|
||||
␍
|
||||
yield␍
|
||||
␍
|
||||
# 停止Redis事件总线␍
|
||||
try:␍
|
||||
from app.event_bus import get_event_bus␍
|
||||
get_event_bus().stop()␍
|
||||
logger.info("[EventBus] Redis事件总线已停止")␍
|
||||
except Exception:␍
|
||||
pass␍
|
||||
␍
|
||||
# 优雅关闭:停止所有 Evolution Pipeline␍
|
||||
try:␍
|
||||
from app.services.evolution_service import EvolutionWatcherManager␍
|
||||
EvolutionWatcherManager().shutdown_all()␍
|
||||
logger.info("[Evolution] 所有Pipeline已停止")␍
|
||||
except Exception:␍
|
||||
pass␍
|
||||
␍
|
||||
logger.info("Qihu-AIFlow 正在关闭...")␍
|
||||
␍
|
||||
␍
|
||||
app = FastAPI(␍
|
||||
title="Qihu-AIFlow - 智能自动化PaaS平台",␍
|
||||
version="1.0.0",␍
|
||||
lifespan=lifespan,␍
|
||||
)␍
|
||||
␍
|
||||
# API调用追踪中间件␍
|
||||
app.add_middleware(APITrackingMiddleware)␍
|
||||
␍
|
||||
# CORS配置␍
|
||||
app.add_middleware(␍
|
||||
CORSMiddleware,␍
|
||||
allow_origins=["*"],␍
|
||||
allow_credentials=True,␍
|
||||
allow_methods=["*"],␍
|
||||
allow_headers=["*"],␍
|
||||
)␍
|
||||
␍
|
||||
# 前台 API␍
|
||||
app.include_router(auth.router, prefix="/app/api/auth", tags=["认证"])␍
|
||||
app.include_router(chat.router, prefix="/app/api/chat", tags=["AI聊天"])␍
|
||||
app.include_router(channels.router, prefix="/app/api/channels", tags=["频道"])␍
|
||||
app.include_router(knowledge.router, prefix="/app/api/knowledge", tags=["知识库"])␍
|
||||
app.include_router(market.router, prefix="/app/api/market", tags=["应用市场"])␍
|
||||
app.include_router(token_usage.router, prefix="/app/api/token", tags=["Token驾驶舱"])␍
|
||||
app.include_router(payment.router, prefix="/app/api/payment", tags=["付费中心"])␍
|
||||
app.include_router(profile.router, prefix="/app/api/profile", tags=["个人中心"])␍
|
||||
app.include_router(notifications.router, prefix="/app/api/notifications", tags=["消息通知"])␍
|
||||
app.include_router(assets.router, prefix="/app/api/assets", tags=["资产管理"])␍
|
||||
app.include_router(evolution.router, prefix="/app/api/evolution", tags=["AI自进化"])␍
|
||||
␍
|
||||
# 后台 API␍
|
||||
app.include_router(dashboard.router, prefix="/app/api/admin/dashboard", tags=["后台-仪表盘"])␍
|
||||
app.include_router(users.router, prefix="/app/api/admin/users", tags=["后台-用户管理"])␍
|
||||
app.include_router(models.router, prefix="/app/api/admin/models", tags=["后台-模型管理"])␍
|
||||
app.include_router(settings.router, prefix="/app/api/admin/settings", tags=["后台-网站设置"])␍
|
||||
app.include_router(payment_admin.router, prefix="/app/api/admin/payment", tags=["后台-支付配置"])␍
|
||||
app.include_router(services.router, prefix="/app/api/admin/services", tags=["后台-增值服务"])␍
|
||||
app.include_router(membership_admin.router, prefix="/app/api/admin/membership", tags=["后台-会员管理"])␍
|
||||
app.include_router(ads.router, prefix="/app/api/admin/ads", tags=["后台-广告管理"])␍
|
||||
app.include_router(market_admin.router, prefix="/app/api/admin/market", tags=["后台-应用审核"])␍
|
||||
app.include_router(logs.router, prefix="/app/api/admin/logs", tags=["后台-日志风控"])␍
|
||||
app.include_router(skills_admin.router, prefix="/app/api/admin/skills", tags=["后台-技能管理"])␍
|
||||
"""
|
||||
|
||||
Qihu-AIFlow FastAPI 主应用
|
||||
|
||||
完整后端API,监听18080端口,所有接口以 /app 为前缀
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
import os
|
||||
|
||||
import time
|
||||
|
||||
import uuid
|
||||
|
||||
import logging
|
||||
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
|
||||
|
||||
from app.api import auth, chat, channels, knowledge, market, token_usage, payment, profile, notifications, assets, evolution
|
||||
|
||||
from app.api.admin import dashboard, users, models, settings, payment_admin, services, membership_admin, ads, market_admin, logs, skills_admin, agents_admin, knowledge_admin, storage_admin
|
||||
from app.config import setup_file_logging, SLOW_REQUEST_THRESHOLD
|
||||
|
||||
from app.database import SessionLocal
|
||||
|
||||
from app.models import OperationLog as OperationLogModel, TokenUsageLog as TokenUsageLogModel
|
||||
|
||||
|
||||
|
||||
# 导入平台适配器触发注册(AstrBot借鉴优化方案 P1)
|
||||
|
||||
import app.platform_adapters # noqa: F401 - 触发所有平台适配器装饰器注册
|
||||
|
||||
|
||||
|
||||
# 初始化文件日志(按天轮转)
|
||||
|
||||
access_logger = setup_file_logging()
|
||||
|
||||
logger = logging.getLogger("aiflow")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class APITrackingMiddleware(BaseHTTPMiddleware):
|
||||
|
||||
"""API调用统计中间件 - 记录请求耗时,标记慢请求"""
|
||||
|
||||
|
||||
|
||||
async def dispatch(self, request: Request, call_next):
|
||||
|
||||
request_id = str(uuid.uuid4())
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
path = request.url.path
|
||||
|
||||
method = request.method
|
||||
|
||||
|
||||
|
||||
# 跳过健康检查和静态资源
|
||||
|
||||
skip_paths = ["/app/api/health", "/favicon.ico"]
|
||||
|
||||
should_skip = any(path.startswith(p) for p in skip_paths)
|
||||
|
||||
|
||||
|
||||
try:
|
||||
|
||||
response = await call_next(request)
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
status_code = response.status_code
|
||||
|
||||
|
||||
|
||||
if not should_skip:
|
||||
|
||||
access_logger.info(
|
||||
|
||||
f"[{request_id}] {method} {path} -> {status_code} ({elapsed:.3f}s)"
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
||||
# 异步记录API调用统计
|
||||
|
||||
try:
|
||||
|
||||
db = SessionLocal()
|
||||
|
||||
try:
|
||||
|
||||
_record_api_call(db, method, path, status_code, elapsed)
|
||||
|
||||
finally:
|
||||
|
||||
db.close()
|
||||
|
||||
except Exception as e:
|
||||
|
||||
logger.warning(f"API统计记录失败: {e}")
|
||||
|
||||
|
||||
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
if not should_skip:
|
||||
|
||||
access_logger.info(
|
||||
|
||||
f"[{request_id}] {method} {path} -> ERROR ({elapsed:.3f}s): {str(e)}"
|
||||
|
||||
)
|
||||
|
||||
raise
|
||||
|
||||
|
||||
|
||||
async def __call__(self, scope, receive, send):
|
||||
|
||||
if scope["type"] != "http":
|
||||
|
||||
await self.app(scope, receive, send)
|
||||
|
||||
return
|
||||
|
||||
await super().__call__(scope, receive, send)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def _record_api_call(db, method: str, path: str, status_code: int, elapsed: float):
|
||||
|
||||
"""记录API调用到数据库统计表"""
|
||||
|
||||
# 简化路径以减少基数(去掉动态ID)
|
||||
|
||||
parts = path.split("/")
|
||||
|
||||
simplified_parts = []
|
||||
|
||||
for part in parts:
|
||||
|
||||
# 将UUID样式的路径段替换为{id}
|
||||
|
||||
if len(part) == 36 and part.count("-") == 4:
|
||||
|
||||
simplified_parts.append("{id}")
|
||||
|
||||
# 将纯数字路径段替换为{id}
|
||||
|
||||
elif part.isdigit():
|
||||
|
||||
simplified_parts.append("{id}")
|
||||
|
||||
else:
|
||||
|
||||
simplified_parts.append(part)
|
||||
|
||||
simplified_path = "/".join(simplified_parts)
|
||||
|
||||
|
||||
|
||||
# 统计记录通过数据库实现,简化版记录到日志
|
||||
|
||||
is_error = status_code >= 400
|
||||
|
||||
is_slow = elapsed >= SLOW_REQUEST_THRESHOLD
|
||||
|
||||
|
||||
|
||||
if is_slow:
|
||||
|
||||
logger.warning(
|
||||
|
||||
f"慢请求: {method} {simplified_path} -> {status_code} ({elapsed:.3f}s, 阈值: {SLOW_REQUEST_THRESHOLD}s)"
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
|
||||
async def lifespan(app: FastAPI):
|
||||
|
||||
logger.info("Qihu-AIFlow v2.0.0 启动中...")
|
||||
|
||||
|
||||
|
||||
# 启动后台 Evolution Pipeline(知识库图谱已存在的用户配置自动恢复运行)
|
||||
|
||||
try:
|
||||
|
||||
from app.services.evolution_service import EvolutionWatcherManager
|
||||
|
||||
from app.models import UserEvolutionConfig
|
||||
|
||||
db = SessionLocal()
|
||||
|
||||
try:
|
||||
|
||||
configs = db.query(UserEvolutionConfig).filter(
|
||||
|
||||
UserEvolutionConfig.watcher_status == "running"
|
||||
|
||||
).all()
|
||||
|
||||
if configs:
|
||||
|
||||
watcher = EvolutionWatcherManager()
|
||||
|
||||
for cfg in configs:
|
||||
|
||||
try:
|
||||
|
||||
watcher.start_user_evolution(cfg.user_id, lambda: SessionLocal())
|
||||
|
||||
logger.info(f"[Evolution] 恢复用户 {cfg.user_id} 的进化Pipeline")
|
||||
|
||||
except Exception as e:
|
||||
|
||||
logger.warning(f"[Evolution] 恢复用户 {cfg.user_id} 失败: {e}")
|
||||
|
||||
logger.info(f"[Evolution] 已恢复 {len(configs)} 个用户的进化Pipeline")
|
||||
|
||||
else:
|
||||
|
||||
logger.info("[Evolution] 无待恢复的进化配置")
|
||||
|
||||
finally:
|
||||
|
||||
db.close()
|
||||
|
||||
except Exception as e:
|
||||
|
||||
logger.warning(f"[Evolution] Pipeline自动启动异常(不影响正常服务): {e}")
|
||||
|
||||
|
||||
|
||||
# 启动Redis事件总线
|
||||
|
||||
try:
|
||||
|
||||
from app.event_bus import get_event_bus
|
||||
|
||||
event_bus = get_event_bus()
|
||||
|
||||
event_bus.start(num_consumers=2)
|
||||
|
||||
logger.info("[EventBus] Redis事件总线已启动")
|
||||
|
||||
except Exception as e:
|
||||
|
||||
logger.warning(f"[EventBus] 启动失败(不影响正常服务): {e}")
|
||||
|
||||
|
||||
|
||||
logger.info("Qihu-AIFlow v2.0.0 启动完成")
|
||||
|
||||
|
||||
|
||||
yield
|
||||
|
||||
|
||||
|
||||
# 停止Redis事件总线
|
||||
|
||||
try:
|
||||
|
||||
from app.event_bus import get_event_bus
|
||||
|
||||
get_event_bus().stop()
|
||||
|
||||
logger.info("[EventBus] Redis事件总线已停止")
|
||||
|
||||
except Exception:
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
# 优雅关闭:停止所有 Evolution Pipeline
|
||||
|
||||
try:
|
||||
|
||||
from app.services.evolution_service import EvolutionWatcherManager
|
||||
|
||||
EvolutionWatcherManager().shutdown_all()
|
||||
|
||||
logger.info("[Evolution] 所有Pipeline已停止")
|
||||
|
||||
except Exception:
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
logger.info("Qihu-AIFlow 正在关闭...")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
|
||||
title="Qihu-AIFlow - 智能自动化PaaS平台",
|
||||
|
||||
version="1.0.0",
|
||||
|
||||
lifespan=lifespan,
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
||||
# API调用追踪中间件
|
||||
|
||||
app.add_middleware(APITrackingMiddleware)
|
||||
|
||||
|
||||
|
||||
# CORS配置
|
||||
|
||||
app.add_middleware(
|
||||
|
||||
CORSMiddleware,
|
||||
|
||||
allow_origins=["*"],
|
||||
|
||||
allow_credentials=True,
|
||||
|
||||
allow_methods=["*"],
|
||||
|
||||
allow_headers=["*"],
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
||||
# 前台 API
|
||||
|
||||
app.include_router(auth.router, prefix="/app/api/auth", tags=["认证"])
|
||||
|
||||
app.include_router(chat.router, prefix="/app/api/chat", tags=["AI聊天"])
|
||||
|
||||
app.include_router(channels.router, prefix="/app/api/channels", tags=["频道"])
|
||||
|
||||
app.include_router(knowledge.router, prefix="/app/api/knowledge", tags=["知识库"])
|
||||
|
||||
app.include_router(market.router, prefix="/app/api/market", tags=["应用市场"])
|
||||
|
||||
app.include_router(token_usage.router, prefix="/app/api/token", tags=["Token驾驶舱"])
|
||||
|
||||
app.include_router(payment.router, prefix="/app/api/payment", tags=["付费中心"])
|
||||
|
||||
app.include_router(profile.router, prefix="/app/api/profile", tags=["个人中心"])
|
||||
|
||||
app.include_router(notifications.router, prefix="/app/api/notifications", tags=["消息通知"])
|
||||
|
||||
app.include_router(assets.router, prefix="/app/api/assets", tags=["资产管理"])
|
||||
|
||||
app.include_router(evolution.router, prefix="/app/api/evolution", tags=["AI自进化"])
|
||||
|
||||
|
||||
|
||||
# 后台 API
|
||||
|
||||
app.include_router(dashboard.router, prefix="/app/api/admin/dashboard", tags=["后台-仪表盘"])
|
||||
|
||||
app.include_router(users.router, prefix="/app/api/admin/users", tags=["后台-用户管理"])
|
||||
|
||||
app.include_router(models.router, prefix="/app/api/admin/models", tags=["后台-模型管理"])
|
||||
|
||||
app.include_router(settings.router, prefix="/app/api/admin/settings", tags=["后台-网站设置"])
|
||||
|
||||
app.include_router(payment_admin.router, prefix="/app/api/admin/payment", tags=["后台-支付配置"])
|
||||
|
||||
app.include_router(services.router, prefix="/app/api/admin/services", tags=["后台-增值服务"])
|
||||
|
||||
app.include_router(membership_admin.router, prefix="/app/api/admin/membership", tags=["后台-会员管理"])
|
||||
|
||||
app.include_router(ads.router, prefix="/app/api/admin/ads", tags=["后台-广告管理"])
|
||||
|
||||
app.include_router(market_admin.router, prefix="/app/api/admin/market", tags=["后台-应用审核"])
|
||||
|
||||
app.include_router(logs.router, prefix="/app/api/admin/logs", tags=["后台-日志风控"])
|
||||
|
||||
app.include_router(skills_admin.router, prefix="/app/api/admin/skills", tags=["后台-技能管理"])
|
||||
|
||||
app.include_router(agents_admin.router, prefix="/app/api/admin/agents", tags=["后台-Agent管理"])
|
||||
app.include_router(knowledge_admin.router, prefix="/app/api/admin/knowledge", tags=["后台-知识库管理"])
|
||||
␍
|
||||
␍
|
||||
@app.get("/app/api/health")␍
|
||||
async def health_check():␍
|
||||
return {"status": "ok", "service": "Qihu-AIFlow"}␍
|
||||
app.include_router(storage_admin.router, prefix="/app/api/admin/storage", tags=["后台-存储配置"])
|
||||
|
||||
|
||||
|
||||
|
||||
@app.get("/app/api/health")
|
||||
|
||||
async def health_check():
|
||||
|
||||
return {"status": "ok", "service": "Qihu-AIFlow"}
|
||||
|
||||
Reference in new issue
Block a user