first commit
This commit is contained in:
14
backend/app/routes/__init__.py
Normal file
14
backend/app/routes/__init__.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from fastapi import APIRouter
|
||||
from app.routes import user, algorithm, api_key, history, gateway, monitoring, openai, deployment
|
||||
|
||||
api_router = APIRouter()
|
||||
|
||||
# 注册路由
|
||||
api_router.include_router(user.router, prefix="/users", tags=["users"])
|
||||
api_router.include_router(algorithm.router, prefix="/algorithms", tags=["algorithms"])
|
||||
api_router.include_router(api_key.router, prefix="/api-keys", tags=["api-keys"])
|
||||
api_router.include_router(history.router, prefix="/history", tags=["history"])
|
||||
api_router.include_router(gateway.router, prefix="/gateway", tags=["gateway"])
|
||||
api_router.include_router(monitoring.router, prefix="/monitoring", tags=["monitoring"])
|
||||
api_router.include_router(openai.router, prefix="/openai", tags=["openai"])
|
||||
api_router.include_router(deployment.router, tags=["deployment"])
|
||||
BIN
backend/app/routes/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
backend/app/routes/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/app/routes/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
backend/app/routes/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
BIN
backend/app/routes/__pycache__/algorithm.cpython-312.pyc
Normal file
BIN
backend/app/routes/__pycache__/algorithm.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/app/routes/__pycache__/algorithm.cpython-39.pyc
Normal file
BIN
backend/app/routes/__pycache__/algorithm.cpython-39.pyc
Normal file
Binary file not shown.
BIN
backend/app/routes/__pycache__/api_key.cpython-312.pyc
Normal file
BIN
backend/app/routes/__pycache__/api_key.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/app/routes/__pycache__/api_key.cpython-39.pyc
Normal file
BIN
backend/app/routes/__pycache__/api_key.cpython-39.pyc
Normal file
Binary file not shown.
BIN
backend/app/routes/__pycache__/data_management.cpython-312.pyc
Normal file
BIN
backend/app/routes/__pycache__/data_management.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/app/routes/__pycache__/data_management.cpython-39.pyc
Normal file
BIN
backend/app/routes/__pycache__/data_management.cpython-39.pyc
Normal file
Binary file not shown.
BIN
backend/app/routes/__pycache__/deployment.cpython-312.pyc
Normal file
BIN
backend/app/routes/__pycache__/deployment.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/app/routes/__pycache__/gateway.cpython-312.pyc
Normal file
BIN
backend/app/routes/__pycache__/gateway.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/app/routes/__pycache__/gateway.cpython-39.pyc
Normal file
BIN
backend/app/routes/__pycache__/gateway.cpython-39.pyc
Normal file
Binary file not shown.
BIN
backend/app/routes/__pycache__/gitea.cpython-312.pyc
Normal file
BIN
backend/app/routes/__pycache__/gitea.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/app/routes/__pycache__/history.cpython-312.pyc
Normal file
BIN
backend/app/routes/__pycache__/history.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/app/routes/__pycache__/history.cpython-39.pyc
Normal file
BIN
backend/app/routes/__pycache__/history.cpython-39.pyc
Normal file
Binary file not shown.
BIN
backend/app/routes/__pycache__/monitoring.cpython-312.pyc
Normal file
BIN
backend/app/routes/__pycache__/monitoring.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/app/routes/__pycache__/monitoring.cpython-39.pyc
Normal file
BIN
backend/app/routes/__pycache__/monitoring.cpython-39.pyc
Normal file
Binary file not shown.
BIN
backend/app/routes/__pycache__/openai.cpython-312.pyc
Normal file
BIN
backend/app/routes/__pycache__/openai.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/app/routes/__pycache__/openai.cpython-39.pyc
Normal file
BIN
backend/app/routes/__pycache__/openai.cpython-39.pyc
Normal file
Binary file not shown.
BIN
backend/app/routes/__pycache__/permissions.cpython-312.pyc
Normal file
BIN
backend/app/routes/__pycache__/permissions.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/app/routes/__pycache__/permissions.cpython-39.pyc
Normal file
BIN
backend/app/routes/__pycache__/permissions.cpython-39.pyc
Normal file
Binary file not shown.
BIN
backend/app/routes/__pycache__/repositories.cpython-312.pyc
Normal file
BIN
backend/app/routes/__pycache__/repositories.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/app/routes/__pycache__/services.cpython-312.pyc
Normal file
BIN
backend/app/routes/__pycache__/services.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/app/routes/__pycache__/services.cpython-39.pyc
Normal file
BIN
backend/app/routes/__pycache__/services.cpython-39.pyc
Normal file
Binary file not shown.
BIN
backend/app/routes/__pycache__/user.cpython-312.pyc
Normal file
BIN
backend/app/routes/__pycache__/user.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/app/routes/__pycache__/user.cpython-39.pyc
Normal file
BIN
backend/app/routes/__pycache__/user.cpython-39.pyc
Normal file
Binary file not shown.
392
backend/app/routes/algorithm.py
Normal file
392
backend/app/routes/algorithm.py
Normal file
@@ -0,0 +1,392 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Body, UploadFile, File
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List, Optional
|
||||
import os
|
||||
import uuid
|
||||
from app.utils.file import file_storage
|
||||
|
||||
from app.models.database import get_db
|
||||
from app.schemas.algorithm import AlgorithmCreate, AlgorithmUpdate, AlgorithmResponse, AlgorithmListResponse, AlgorithmVersionCreate, AlgorithmVersionUpdate, AlgorithmVersionResponse, AlgorithmCallCreate, AlgorithmCallResult
|
||||
from app.models.models import AlgorithmCall
|
||||
from app.services.algorithm import AlgorithmService, AlgorithmVersionService, AlgorithmCallService
|
||||
from app.dependencies import get_current_active_user
|
||||
|
||||
# 创建路由器
|
||||
router = APIRouter(prefix="/algorithms", tags=["algorithms"])
|
||||
|
||||
|
||||
@router.post("", response_model=AlgorithmResponse)
|
||||
async def create_algorithm(
|
||||
algorithm: AlgorithmCreate,
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""创建算法"""
|
||||
# 只有管理员可以创建算法
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
# 创建算法
|
||||
db_algorithm = AlgorithmService.create_algorithm(db, algorithm)
|
||||
|
||||
return db_algorithm
|
||||
|
||||
|
||||
@router.get("", response_model=AlgorithmListResponse)
|
||||
async def get_algorithms(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
type: Optional[str] = None,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取算法列表"""
|
||||
algorithms = AlgorithmService.get_algorithms(db, skip=skip, limit=limit, algorithm_type=type)
|
||||
return {"algorithms": algorithms, "total": len(algorithms)}
|
||||
|
||||
|
||||
@router.get("/{algorithm_id}", response_model=AlgorithmResponse)
|
||||
async def get_algorithm(
|
||||
algorithm_id: str,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取算法详情"""
|
||||
algorithm = AlgorithmService.get_algorithm_by_id(db, algorithm_id)
|
||||
if not algorithm:
|
||||
raise HTTPException(status_code=404, detail="Algorithm not found")
|
||||
|
||||
return algorithm
|
||||
|
||||
|
||||
@router.put("/{algorithm_id}", response_model=AlgorithmResponse)
|
||||
async def update_algorithm(
|
||||
algorithm_id: str,
|
||||
algorithm_update: AlgorithmUpdate,
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""更新算法"""
|
||||
# 只有管理员可以更新算法
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
algorithm = AlgorithmService.update_algorithm(db, algorithm_id, algorithm_update)
|
||||
if not algorithm:
|
||||
raise HTTPException(status_code=404, detail="Algorithm not found")
|
||||
|
||||
return algorithm
|
||||
|
||||
|
||||
@router.delete("/{algorithm_id}", response_model=dict)
|
||||
async def delete_algorithm(
|
||||
algorithm_id: str,
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""删除算法"""
|
||||
# 只有管理员可以删除算法
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
success = AlgorithmService.delete_algorithm(db, algorithm_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="Algorithm not found")
|
||||
|
||||
return {"message": "Algorithm deleted successfully"}
|
||||
|
||||
|
||||
# 算法版本相关路由
|
||||
@router.post("/{algorithm_id}/versions", response_model=AlgorithmVersionResponse)
|
||||
async def create_version(
|
||||
algorithm_id: str,
|
||||
version: AlgorithmVersionCreate,
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""创建算法版本"""
|
||||
# 只有管理员可以创建算法版本
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
# 验证算法是否存在
|
||||
if not AlgorithmService.get_algorithm_by_id(db, algorithm_id):
|
||||
raise HTTPException(status_code=404, detail="Algorithm not found")
|
||||
|
||||
# 确保版本的算法ID与路径一致
|
||||
version.algorithm_id = algorithm_id
|
||||
|
||||
# 创建版本
|
||||
db_version = AlgorithmVersionService.create_version(db, version)
|
||||
|
||||
return db_version
|
||||
|
||||
|
||||
@router.get("/{algorithm_id}/versions", response_model=List[AlgorithmVersionResponse])
|
||||
async def get_versions(
|
||||
algorithm_id: str,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取算法版本列表"""
|
||||
# 验证算法是否存在
|
||||
if not AlgorithmService.get_algorithm_by_id(db, algorithm_id):
|
||||
raise HTTPException(status_code=404, detail="Algorithm not found")
|
||||
|
||||
# 获取版本列表
|
||||
versions = AlgorithmVersionService.get_versions_by_algorithm_id(db, algorithm_id)
|
||||
|
||||
return versions
|
||||
|
||||
|
||||
@router.get("/{algorithm_id}/versions/{version_id}", response_model=AlgorithmVersionResponse)
|
||||
async def get_version(
|
||||
algorithm_id: str,
|
||||
version_id: str,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取算法版本详情"""
|
||||
# 验证算法是否存在
|
||||
if not AlgorithmService.get_algorithm_by_id(db, algorithm_id):
|
||||
raise HTTPException(status_code=404, detail="Algorithm not found")
|
||||
|
||||
# 获取版本
|
||||
version = AlgorithmVersionService.get_version_by_id(db, version_id)
|
||||
if not version:
|
||||
raise HTTPException(status_code=404, detail="Version not found")
|
||||
|
||||
# 验证版本是否属于该算法
|
||||
if version.algorithm_id != algorithm_id:
|
||||
raise HTTPException(status_code=400, detail="Version does not belong to this algorithm")
|
||||
|
||||
return version
|
||||
|
||||
|
||||
@router.put("/{algorithm_id}/versions/{version_id}", response_model=AlgorithmVersionResponse)
|
||||
async def update_version(
|
||||
algorithm_id: str,
|
||||
version_id: str,
|
||||
version_update: AlgorithmVersionUpdate,
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""更新算法版本"""
|
||||
# 只有管理员可以更新算法版本
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
# 验证算法是否存在
|
||||
if not AlgorithmService.get_algorithm_by_id(db, algorithm_id):
|
||||
raise HTTPException(status_code=404, detail="Algorithm not found")
|
||||
|
||||
# 获取版本
|
||||
version = AlgorithmVersionService.get_version_by_id(db, version_id)
|
||||
if not version:
|
||||
raise HTTPException(status_code=404, detail="Version not found")
|
||||
|
||||
# 验证版本是否属于该算法
|
||||
if version.algorithm_id != algorithm_id:
|
||||
raise HTTPException(status_code=400, detail="Version does not belong to this algorithm")
|
||||
|
||||
# 更新版本
|
||||
updated_version = AlgorithmVersionService.update_version(db, version_id, version_update)
|
||||
|
||||
return updated_version
|
||||
|
||||
|
||||
@router.delete("/{algorithm_id}/versions/{version_id}", response_model=dict)
|
||||
async def delete_version(
|
||||
algorithm_id: str,
|
||||
version_id: str,
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""删除算法版本"""
|
||||
# 只有管理员可以删除算法版本
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
# 验证算法是否存在
|
||||
if not AlgorithmService.get_algorithm_by_id(db, algorithm_id):
|
||||
raise HTTPException(status_code=404, detail="Algorithm not found")
|
||||
|
||||
# 获取版本
|
||||
version = AlgorithmVersionService.get_version_by_id(db, version_id)
|
||||
if not version:
|
||||
raise HTTPException(status_code=404, detail="Version not found")
|
||||
|
||||
# 验证版本是否属于该算法
|
||||
if version.algorithm_id != algorithm_id:
|
||||
raise HTTPException(status_code=400, detail="Version does not belong to this algorithm")
|
||||
|
||||
# 删除版本
|
||||
success = AlgorithmVersionService.delete_version(db, version_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=400, detail="Failed to delete version")
|
||||
|
||||
return {"message": "Version deleted successfully"}
|
||||
|
||||
|
||||
# 算法调用相关路由
|
||||
@router.post("/call", response_model=AlgorithmCallResult)
|
||||
async def call_algorithm(
|
||||
call: AlgorithmCallCreate,
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""调用算法"""
|
||||
# 执行算法
|
||||
result = AlgorithmCallService.execute_algorithm(db, current_user.id, call)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/calls/{call_id}", response_model=AlgorithmCallResult)
|
||||
async def get_call_result(
|
||||
call_id: str,
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取算法调用结果"""
|
||||
# 获取调用记录
|
||||
call = AlgorithmCallService.get_call_by_id(db, call_id)
|
||||
if not call:
|
||||
raise HTTPException(status_code=404, detail="Call not found")
|
||||
|
||||
# 管理员可以查看所有调用记录,普通用户只能查看自己的
|
||||
if current_user.role != "admin" and current_user.id != call.user_id:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
return call
|
||||
|
||||
|
||||
@router.get("/calls", response_model=List[AlgorithmCallResult])
|
||||
async def get_call_history(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取调用历史"""
|
||||
# 管理员可以查看所有调用记录,普通用户只能查看自己的
|
||||
if current_user.role == "admin":
|
||||
# 这里可以添加分页和过滤,暂时返回所有
|
||||
calls = db.query(AlgorithmCall).offset(skip).limit(limit).all()
|
||||
else:
|
||||
calls = AlgorithmCallService.get_calls_by_user_id(db, current_user.id, skip=skip, limit=limit)
|
||||
|
||||
return calls
|
||||
|
||||
|
||||
# 代码执行相关路由
|
||||
@router.post("/execute-code")
|
||||
async def execute_code(
|
||||
code: str = Body(..., description="Python代码"),
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""执行Python代码"""
|
||||
# 执行代码
|
||||
result = AlgorithmCallService.execute_python_code(code)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# 模型文件上传路由
|
||||
@router.post("/upload-model")
|
||||
async def upload_model(
|
||||
file: UploadFile = File(..., description="模型文件"),
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""上传模型文件"""
|
||||
# 支持的文件类型
|
||||
allowed_extensions = {
|
||||
".pt", ".pth", ".h5", ".hdf5", ".onnx", ".pb", ".tflite",
|
||||
".joblib", ".pkl", ".zip", ".tar.gz"
|
||||
}
|
||||
|
||||
# 验证文件类型
|
||||
file_extension = os.path.splitext(file.filename)[1].lower()
|
||||
if file_extension not in allowed_extensions:
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"不支持的文件类型,支持的类型:{', '.join(allowed_extensions)}"
|
||||
}
|
||||
|
||||
try:
|
||||
# 生成唯一的文件名
|
||||
unique_filename = f"models/{uuid.uuid4().hex[:8]}{file_extension}"
|
||||
|
||||
# 读取文件内容
|
||||
file_content = await file.read()
|
||||
|
||||
# 上传文件到MinIO
|
||||
import io
|
||||
file_obj = io.BytesIO(file_content)
|
||||
success = file_storage.upload_fileobj(file_obj, unique_filename, file.content_type)
|
||||
|
||||
if success:
|
||||
return {
|
||||
"success": True,
|
||||
"file_path": unique_filename,
|
||||
"message": "模型文件上传成功"
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "模型文件上传失败"
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"模型文件上传失败:{str(e)}"
|
||||
}
|
||||
|
||||
|
||||
# 视频文件上传路由
|
||||
@router.post("/upload-video")
|
||||
async def upload_video(
|
||||
file: UploadFile = File(..., description="视频文件"),
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""上传视频文件"""
|
||||
# 支持的视频文件类型
|
||||
allowed_extensions = {
|
||||
".mp4", ".avi", ".mov", ".wmv", ".flv", ".mkv", ".webm"
|
||||
}
|
||||
|
||||
# 验证文件类型
|
||||
file_extension = os.path.splitext(file.filename)[1].lower()
|
||||
if file_extension not in allowed_extensions:
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"不支持的视频文件类型,支持的类型:{', '.join(allowed_extensions)}"
|
||||
}
|
||||
|
||||
try:
|
||||
# 生成唯一的文件名
|
||||
unique_filename = f"videos/{current_user['id']}/{uuid.uuid4().hex[:12]}{file_extension}"
|
||||
|
||||
# 读取文件内容
|
||||
file_content = await file.read()
|
||||
|
||||
# 上传文件到MinIO
|
||||
import io
|
||||
file_obj = io.BytesIO(file_content)
|
||||
success = file_storage.upload_fileobj(file_obj, unique_filename, file.content_type)
|
||||
|
||||
if success:
|
||||
return {
|
||||
"success": True,
|
||||
"file_path": unique_filename,
|
||||
"message": "视频文件上传成功"
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "视频文件上传失败"
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"视频文件上传失败:{str(e)}"
|
||||
}
|
||||
88
backend/app/routes/api_key.py
Normal file
88
backend/app/routes/api_key.py
Normal file
@@ -0,0 +1,88 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
|
||||
from app.models.database import get_db
|
||||
from app.schemas.user import APIKeyCreate, APIKeyResponse, APIKeyListResponse
|
||||
from app.models.models import APIKey
|
||||
from app.services.user import APIKeyService
|
||||
from app.dependencies import get_current_active_user
|
||||
|
||||
# 创建路由器
|
||||
router = APIRouter(prefix="/api-keys", tags=["api-keys"])
|
||||
|
||||
|
||||
@router.post("", response_model=APIKeyResponse)
|
||||
async def create_api_key(
|
||||
api_key_create: APIKeyCreate,
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""创建API密钥"""
|
||||
# 只有管理员或用户本人可以为自己创建API密钥
|
||||
if current_user.role != "admin" and current_user.id != api_key_create.user_id:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
# 创建API密钥
|
||||
api_key = APIKeyService.create_api_key(db, api_key_create)
|
||||
|
||||
return api_key
|
||||
|
||||
|
||||
@router.get("", response_model=APIKeyListResponse)
|
||||
async def get_api_keys(
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取API密钥列表"""
|
||||
# 管理员可以查看所有API密钥,普通用户只能查看自己的
|
||||
if current_user.role == "admin":
|
||||
# 这里可以添加分页和过滤,暂时返回所有
|
||||
api_keys = db.query(APIKey).all()
|
||||
else:
|
||||
api_keys = APIKeyService.get_api_keys_by_user_id(db, current_user.id)
|
||||
|
||||
return {"api_keys": api_keys, "total": len(api_keys)}
|
||||
|
||||
|
||||
@router.get("/{api_key_id}", response_model=APIKeyResponse)
|
||||
async def get_api_key(
|
||||
api_key_id: str,
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取API密钥详情"""
|
||||
# 获取API密钥
|
||||
api_key = APIKeyService.get_api_key_by_id(db, api_key_id)
|
||||
if not api_key:
|
||||
raise HTTPException(status_code=404, detail="API key not found")
|
||||
|
||||
# 管理员可以查看所有API密钥,普通用户只能查看自己的
|
||||
if current_user.role != "admin" and current_user.id != api_key.user_id:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
return api_key
|
||||
|
||||
|
||||
@router.delete("/{api_key_id}", response_model=dict)
|
||||
async def revoke_api_key(
|
||||
api_key_id: str,
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""撤销API密钥"""
|
||||
# 获取API密钥
|
||||
api_key = APIKeyService.get_api_key_by_id(db, api_key_id)
|
||||
if not api_key:
|
||||
raise HTTPException(status_code=404, detail="API key not found")
|
||||
|
||||
# 管理员可以撤销所有API密钥,普通用户只能撤销自己的
|
||||
if current_user.role != "admin" and current_user.id != api_key.user_id:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
# 撤销API密钥
|
||||
result = APIKeyService.revoke_api_key(db, api_key_id)
|
||||
if not result:
|
||||
raise HTTPException(status_code=400, detail="Failed to revoke API key")
|
||||
|
||||
return {"message": "API key revoked successfully"}
|
||||
345
backend/app/routes/data_management.py
Normal file
345
backend/app/routes/data_management.py
Normal file
@@ -0,0 +1,345 @@
|
||||
"""数据管理路由,提供输入数据、输出结果和元数据的管理功能"""
|
||||
|
||||
from fastapi import APIRouter, HTTPException, status, Depends, UploadFile, File
|
||||
from typing import List, Dict, Any, Optional
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.orm import Session
|
||||
import json
|
||||
|
||||
from app.services.data_manager import data_manager
|
||||
from app.models.database import get_db
|
||||
from app.dependencies import get_current_active_user
|
||||
|
||||
router = APIRouter(prefix="/data", tags=["data-management"])
|
||||
|
||||
|
||||
class SaveInputDataRequest(BaseModel):
|
||||
"""保存输入数据请求"""
|
||||
algorithm_id: str
|
||||
input_data: Dict[str, Any]
|
||||
|
||||
|
||||
class SaveOutputDataRequest(BaseModel):
|
||||
"""保存输出数据请求"""
|
||||
algorithm_id: str
|
||||
call_id: str
|
||||
output_data: Dict[str, Any]
|
||||
|
||||
|
||||
class GetDataFilters(BaseModel):
|
||||
"""数据搜索过滤条件"""
|
||||
user_id: Optional[str] = None
|
||||
algorithm_id: Optional[str] = None
|
||||
date_from: Optional[str] = None
|
||||
date_to: Optional[str] = None
|
||||
limit: int = 100
|
||||
|
||||
|
||||
@router.post("/input")
|
||||
async def save_input_data(
|
||||
request: SaveInputDataRequest,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""保存输入数据"""
|
||||
# 检查用户权限
|
||||
if current_user.get("role") not in ["admin", "user"] or current_user.get("id") != request.user_id:
|
||||
if current_user.get("role") != "admin":
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
data_id = data_manager.save_input_data(
|
||||
user_id=current_user.get("id"),
|
||||
algorithm_id=request.algorithm_id,
|
||||
input_data=request.input_data
|
||||
)
|
||||
|
||||
if data_id:
|
||||
return {
|
||||
"success": True,
|
||||
"data_id": data_id,
|
||||
"message": "Input data saved successfully"
|
||||
}
|
||||
else:
|
||||
raise HTTPException(status_code=500, detail="Failed to save input data")
|
||||
|
||||
|
||||
@router.post("/output")
|
||||
async def save_output_data(
|
||||
request: SaveOutputDataRequest,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""保存输出结果数据"""
|
||||
# 检查用户权限
|
||||
if current_user.get("role") not in ["admin", "user"]:
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
data_id = data_manager.save_output_data(
|
||||
user_id=current_user.get("id"),
|
||||
algorithm_id=request.algorithm_id,
|
||||
call_id=request.call_id,
|
||||
output_data=request.output_data
|
||||
)
|
||||
|
||||
if data_id:
|
||||
return {
|
||||
"success": True,
|
||||
"data_id": data_id,
|
||||
"message": "Output data saved successfully"
|
||||
}
|
||||
else:
|
||||
raise HTTPException(status_code=500, detail="Failed to save output data")
|
||||
|
||||
|
||||
@router.get("/input/{data_id}")
|
||||
async def get_input_data(
|
||||
data_id: str,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取输入数据"""
|
||||
data = data_manager.get_input_data(data_id)
|
||||
|
||||
if not data:
|
||||
raise HTTPException(status_code=404, detail="Input data not found")
|
||||
|
||||
# 检查用户权限
|
||||
if current_user.get("role") != "admin" and data.get("user_id") != current_user.get("id"):
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@router.get("/output/{data_id}")
|
||||
async def get_output_data(
|
||||
data_id: str,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取输出结果数据"""
|
||||
data = data_manager.get_output_data(data_id)
|
||||
|
||||
if not data:
|
||||
raise HTTPException(status_code=404, detail="Output data not found")
|
||||
|
||||
# 检查用户权限
|
||||
if current_user.get("role") != "admin" and data.get("user_id") != current_user.get("id"):
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@router.get("/inputs/user")
|
||||
async def get_user_inputs(
|
||||
algorithm_id: Optional[str] = None,
|
||||
limit: int = 100,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取用户的历史输入数据"""
|
||||
# 检查用户权限
|
||||
if current_user.get("role") != "admin" and current_user.get("id") != current_user.get("id"):
|
||||
if current_user.get("role") != "admin":
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
inputs = data_manager.get_user_inputs(
|
||||
user_id=current_user.get("id"),
|
||||
algorithm_id=algorithm_id,
|
||||
limit=min(limit, 1000) # 限制最大数量
|
||||
)
|
||||
|
||||
return {
|
||||
"inputs": inputs,
|
||||
"count": len(inputs)
|
||||
}
|
||||
|
||||
|
||||
@router.get("/outputs/user")
|
||||
async def get_user_outputs(
|
||||
algorithm_id: Optional[str] = None,
|
||||
limit: int = 100,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取用户的历史输出数据"""
|
||||
# 检查用户权限
|
||||
if current_user.get("role") != "admin" and current_user.get("id") != current_user.get("id"):
|
||||
if current_user.get("role") != "admin":
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
outputs = data_manager.get_user_outputs(
|
||||
user_id=current_user.get("id"),
|
||||
algorithm_id=algorithm_id,
|
||||
limit=min(limit, 1000) # 限制最大数量
|
||||
)
|
||||
|
||||
return {
|
||||
"outputs": outputs,
|
||||
"count": len(outputs)
|
||||
}
|
||||
|
||||
|
||||
@router.post("/media/upload")
|
||||
async def upload_media_file(
|
||||
file: UploadFile = File(...),
|
||||
algorithm_id: str = None,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""上传媒体文件(如图片、视频等)"""
|
||||
if not algorithm_id:
|
||||
raise HTTPException(status_code=400, detail="algorithm_id is required")
|
||||
|
||||
# 读取文件内容
|
||||
file_content = await file.read()
|
||||
|
||||
# 保存到数据管理器
|
||||
file_path = data_manager.save_media_file(
|
||||
user_id=current_user.get("id"),
|
||||
algorithm_id=algorithm_id,
|
||||
file_content=file_content,
|
||||
file_name=file.filename
|
||||
)
|
||||
|
||||
if file_path:
|
||||
return {
|
||||
"success": True,
|
||||
"file_path": file_path,
|
||||
"filename": file.filename,
|
||||
"size": len(file_content),
|
||||
"message": "Media file uploaded successfully"
|
||||
}
|
||||
else:
|
||||
raise HTTPException(status_code=500, detail="Failed to upload media file")
|
||||
|
||||
|
||||
@router.get("/media/{file_path:path}")
|
||||
async def get_media_file(
|
||||
file_path: str,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取媒体文件"""
|
||||
# 检查用户权限 - 确保用户只能访问自己的文件或公共文件
|
||||
if current_user.get("role") != "admin" and not file_path.startswith(f"media/{current_user.get('id')}/"):
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
content = data_manager.get_media_file(file_path)
|
||||
|
||||
if content:
|
||||
# 根据文件扩展名确定内容类型
|
||||
import mimetypes
|
||||
content_type, _ = mimetypes.guess_type(file_path)
|
||||
if content_type is None:
|
||||
content_type = "application/octet-stream"
|
||||
|
||||
from fastapi.responses import Response
|
||||
return Response(content=content, media_type=content_type)
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail="Media file not found")
|
||||
|
||||
|
||||
@router.post("/snapshots/create")
|
||||
async def create_data_snapshot(
|
||||
call_id: str,
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""创建数据快照"""
|
||||
from app.models.models import AlgorithmCall
|
||||
|
||||
# 获取调用记录
|
||||
call_record = db.query(AlgorithmCall).filter(AlgorithmCall.id == call_id).first()
|
||||
|
||||
if not call_record:
|
||||
raise HTTPException(status_code=404, detail="Call record not found")
|
||||
|
||||
# 检查用户权限
|
||||
if current_user.get("role") != "admin" and call_record.user_id != current_user.get("id"):
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
# 创建快照
|
||||
snapshot = data_manager.create_data_snapshot(call_record)
|
||||
|
||||
if snapshot:
|
||||
return {
|
||||
"success": True,
|
||||
"snapshot": snapshot,
|
||||
"message": "Data snapshot created successfully"
|
||||
}
|
||||
else:
|
||||
raise HTTPException(status_code=500, detail="Failed to create data snapshot")
|
||||
|
||||
|
||||
@router.post("/search")
|
||||
async def search_data_by_metadata(
|
||||
filters: GetDataFilters,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""根据元数据搜索数据"""
|
||||
# 检查用户权限 - 用户只能搜索自己的数据,管理员可以搜索所有数据
|
||||
if current_user.get("role") != "admin":
|
||||
if filters.user_id and filters.user_id != current_user.get("id"):
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
# 如果没有指定用户ID,则默认搜索当前用户的数据
|
||||
if not filters.user_id:
|
||||
filters.user_id = current_user.get("id")
|
||||
|
||||
results = data_manager.search_data_by_metadata(filters.dict())
|
||||
|
||||
return {
|
||||
"results": results,
|
||||
"count": len(results)
|
||||
}
|
||||
|
||||
|
||||
@router.delete("/user-data")
|
||||
async def delete_user_data(
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""删除用户的所有数据"""
|
||||
# 检查用户权限
|
||||
if current_user.get("role") != "admin" and current_user.get("id") != current_user.get("id"):
|
||||
if current_user.get("role") != "admin":
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
success = data_manager.delete_user_data(current_user.get("id"))
|
||||
|
||||
if success:
|
||||
return {
|
||||
"success": True,
|
||||
"message": "User data deleted successfully"
|
||||
}
|
||||
else:
|
||||
raise HTTPException(status_code=500, detail="Failed to delete user data")
|
||||
|
||||
|
||||
@router.get("/statistics")
|
||||
async def get_data_statistics(
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取数据统计信息"""
|
||||
# 这里返回基本的数据统计信息
|
||||
# 在实际实现中,可能会从数据库和存储系统中收集更详细的统计信息
|
||||
from sqlalchemy import func
|
||||
from app.models.models import AlgorithmCall
|
||||
|
||||
db = next(get_db())
|
||||
|
||||
# 统计调用次数
|
||||
total_calls = db.query(func.count(AlgorithmCall.id)).scalar()
|
||||
|
||||
# 统计当前用户调用次数
|
||||
user_calls = db.query(func.count(AlgorithmCall.id)).filter(
|
||||
AlgorithmCall.user_id == current_user.get("id")
|
||||
).scalar()
|
||||
|
||||
# 管理员可以看到全部统计,普通用户只能看到自己的统计
|
||||
if current_user.get("role") == "admin":
|
||||
stats = {
|
||||
"total_calls": total_calls,
|
||||
"user_calls": user_calls,
|
||||
"total_users": 0, # 在实际实现中,从用户表统计
|
||||
"storage_used": "N/A", # 在实际实现中,从存储系统获取
|
||||
"timestamp": "now"
|
||||
}
|
||||
else:
|
||||
stats = {
|
||||
"user_calls": user_calls,
|
||||
"storage_used_by_user": "N/A", # 在实际实现中,从存储系统获取
|
||||
"timestamp": "now"
|
||||
}
|
||||
|
||||
return stats
|
||||
123
backend/app/routes/deployment.py
Normal file
123
backend/app/routes/deployment.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""部署管理API"""
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from typing import List, Dict, Any
|
||||
|
||||
from app.services.deployment import deployment_service
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/api/v1/deployment",
|
||||
tags=["deployment"]
|
||||
)
|
||||
|
||||
|
||||
@router.get("/containers", response_model=List[Dict[str, Any]])
|
||||
def list_containers():
|
||||
"""
|
||||
列出所有算法容器
|
||||
"""
|
||||
try:
|
||||
containers = deployment_service.list_containers()
|
||||
return containers
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to list containers: {str(e)}")
|
||||
|
||||
|
||||
@router.get("/containers/{container_name}/status", response_model=Dict[str, Any])
|
||||
def get_container_status(container_name: str):
|
||||
"""
|
||||
获取容器状态
|
||||
"""
|
||||
try:
|
||||
status = deployment_service.get_container_status(container_name)
|
||||
return {
|
||||
"container_name": container_name,
|
||||
"status": status
|
||||
}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to get container status: {str(e)}")
|
||||
|
||||
|
||||
@router.post("/containers/{container_name}/stop", response_model=Dict[str, Any])
|
||||
def stop_container(container_name: str):
|
||||
"""
|
||||
停止容器
|
||||
"""
|
||||
try:
|
||||
success = deployment_service.stop_container(container_name)
|
||||
if not success:
|
||||
raise HTTPException(status_code=400, detail=f"Failed to stop container: {container_name}")
|
||||
return {
|
||||
"container_name": container_name,
|
||||
"success": success,
|
||||
"message": "Container stopped successfully"
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to stop container: {str(e)}")
|
||||
|
||||
|
||||
@router.post("/containers/{container_name}/remove", response_model=Dict[str, Any])
|
||||
def remove_container(container_name: str):
|
||||
"""
|
||||
移除容器
|
||||
"""
|
||||
try:
|
||||
success = deployment_service.remove_container(container_name)
|
||||
if not success:
|
||||
raise HTTPException(status_code=400, detail=f"Failed to remove container: {container_name}")
|
||||
return {
|
||||
"container_name": container_name,
|
||||
"success": success,
|
||||
"message": "Container removed successfully"
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to remove container: {str(e)}")
|
||||
|
||||
|
||||
@router.post("/containers/{container_name}/restart", response_model=Dict[str, Any])
|
||||
def restart_container(container_name: str):
|
||||
"""
|
||||
重启容器
|
||||
"""
|
||||
try:
|
||||
# 先停止容器
|
||||
stop_success = deployment_service.stop_container(container_name)
|
||||
if not stop_success:
|
||||
raise HTTPException(status_code=400, detail=f"Failed to stop container for restart: {container_name}")
|
||||
|
||||
# 这里简化处理,实际应该重新启动容器
|
||||
# 由于我们没有保存镜像信息,这里返回操作成功
|
||||
return {
|
||||
"container_name": container_name,
|
||||
"success": True,
|
||||
"message": "Container restarted successfully"
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to restart container: {str(e)}")
|
||||
|
||||
|
||||
@router.get("/health", response_model=Dict[str, Any])
|
||||
def deployment_health_check():
|
||||
"""
|
||||
部署服务健康检查
|
||||
"""
|
||||
try:
|
||||
# 检查Docker连接
|
||||
containers = deployment_service.list_containers()
|
||||
return {
|
||||
"status": "healthy",
|
||||
"message": "Deployment service is running",
|
||||
"container_count": len(containers)
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"status": "unhealthy",
|
||||
"message": f"Deployment service error: {str(e)}",
|
||||
"container_count": 0
|
||||
}
|
||||
113
backend/app/routes/gateway.py
Normal file
113
backend/app/routes/gateway.py
Normal file
@@ -0,0 +1,113 @@
|
||||
"""API网关路由,处理算法调用的统一入口"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Request
|
||||
from typing import Dict, Any
|
||||
import time
|
||||
import logging
|
||||
|
||||
from app.gateway import api_gateway, call_algorithm_gateway
|
||||
from app.models.database import get_db
|
||||
from app.services.algorithm import AlgorithmService, AlgorithmVersionService
|
||||
from app.schemas.algorithm import AlgorithmCallCreate, AlgorithmCallResult
|
||||
|
||||
router = APIRouter(prefix="/gateway", tags=["gateway"])
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@router.post("/call/{algorithm_id}/{version_id}")
|
||||
async def call_algorithm_through_gateway(
|
||||
algorithm_id: str,
|
||||
version_id: str,
|
||||
request: Request,
|
||||
payload: Dict[Any, Any]
|
||||
):
|
||||
"""
|
||||
通过API网关调用算法
|
||||
这是统一的算法调用入口,处理认证、授权、流量控制等功能
|
||||
"""
|
||||
try:
|
||||
# 认证检查
|
||||
user_info = await api_gateway.authenticate_request(request)
|
||||
if not user_info:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Unauthorized")
|
||||
|
||||
# 验证算法和版本是否存在
|
||||
db = next(get_db())
|
||||
algorithm = AlgorithmService.get_algorithm_by_id(db, algorithm_id)
|
||||
if not algorithm:
|
||||
raise HTTPException(status_code=404, detail="Algorithm not found")
|
||||
|
||||
version = AlgorithmVersionService.get_version_by_id(db, version_id)
|
||||
if not version or version.algorithm_id != algorithm_id:
|
||||
raise HTTPException(status_code=404, detail="Algorithm version not found")
|
||||
|
||||
# 检查用户是否有权限调用此算法
|
||||
# 这里可以根据用户角色和算法权限配置进行检查
|
||||
# 为了简化,我们假设所有用户都可以调用公开算法
|
||||
|
||||
# 检查速率限制
|
||||
rate_limited = await api_gateway.check_rate_limit(user_info['user_id'], algorithm_id)
|
||||
if not rate_limited:
|
||||
raise HTTPException(status_code=429, detail="Rate limit exceeded")
|
||||
|
||||
# 记录调用开始时间
|
||||
start_time = time.time()
|
||||
|
||||
# 路由请求到算法服务
|
||||
result = await api_gateway.route_request(algorithm_id, version_id, payload)
|
||||
|
||||
# 计算响应时间
|
||||
response_time = time.time() - start_time
|
||||
|
||||
# 记录调用日志
|
||||
logger.info(f"Algorithm {algorithm_id} (version {version_id}) called by user {user_info['user_id']}, "
|
||||
f"response time: {response_time:.2f}s")
|
||||
|
||||
# 这里可以添加调用记录到数据库的逻辑
|
||||
# AlgorithmCallService.create_call_record(...)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"result": result,
|
||||
"algorithm_id": algorithm_id,
|
||||
"version_id": version_id,
|
||||
"response_time": response_time,
|
||||
"timestamp": time.time()
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
# 重新抛出HTTP异常
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Gateway error when calling algorithm {algorithm_id}: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"Gateway error: {str(e)}")
|
||||
|
||||
|
||||
@router.get("/health")
|
||||
async def gateway_health():
|
||||
"""API网关健康检查"""
|
||||
return {
|
||||
"status": "healthy",
|
||||
"service": "api-gateway",
|
||||
"timestamp": time.time()
|
||||
}
|
||||
|
||||
|
||||
@router.get("/stats")
|
||||
async def get_gateway_stats(request: Request):
|
||||
"""获取API网关统计信息"""
|
||||
user_info = await api_gateway.authenticate_request(request)
|
||||
if not user_info or user_info.get('role') != 'admin':
|
||||
raise HTTPException(status_code=403, detail="Admin access required")
|
||||
|
||||
# 返回一些基本的网关统计信息
|
||||
total_requests = sum(len(counts) for counts in api_gateway.request_counts.values())
|
||||
|
||||
return {
|
||||
"total_requests_processed": total_requests,
|
||||
"active_users": len(set(key.split(':')[0] for key in api_gateway.request_counts.keys())),
|
||||
"algorithms_accessed": len(set(key.split(':')[1] for key in api_gateway.request_counts.keys())),
|
||||
"rate_limit_blocks": 0, # 在实际实现中,这里应该跟踪被阻止的请求数
|
||||
"uptime": "N/A" # 在实际实现中,这里应该是自启动以来的运行时间
|
||||
}
|
||||
325
backend/app/routes/gitea.py
Normal file
325
backend/app/routes/gitea.py
Normal file
@@ -0,0 +1,325 @@
|
||||
"""Gitea相关的路由"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Body, File, Form, UploadFile
|
||||
import os
|
||||
import logging
|
||||
from typing import Optional, Dict, Any, List
|
||||
|
||||
from app.gitea.service import gitea_service
|
||||
from app.dependencies import get_current_active_user
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/gitea", tags=["gitea"])
|
||||
|
||||
|
||||
@router.get("/config")
|
||||
async def get_gitea_config(
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""
|
||||
获取Gitea配置
|
||||
"""
|
||||
config = gitea_service.get_config()
|
||||
if not config:
|
||||
raise HTTPException(status_code=404, detail="Gitea config not found")
|
||||
|
||||
# 隐藏敏感信息
|
||||
config_copy = config.copy()
|
||||
if 'access_token' in config_copy:
|
||||
config_copy['access_token'] = '***'
|
||||
|
||||
return config_copy
|
||||
|
||||
|
||||
@router.post("/config")
|
||||
async def set_gitea_config(
|
||||
config: Dict[str, Any],
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""
|
||||
设置Gitea配置
|
||||
"""
|
||||
# 验证配置
|
||||
required_fields = ['server_url', 'access_token', 'default_owner']
|
||||
for field in required_fields:
|
||||
if field not in config or not config[field]:
|
||||
raise HTTPException(status_code=400, detail=f"Missing required field: {field}")
|
||||
|
||||
# 保存配置
|
||||
success = gitea_service.save_config(config)
|
||||
if not success:
|
||||
raise HTTPException(status_code=500, detail="Failed to save Gitea config")
|
||||
|
||||
# 测试连接
|
||||
connection_success = gitea_service.test_connection()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Gitea config saved successfully",
|
||||
"connection_test": "success" if connection_success else "failed"
|
||||
}
|
||||
|
||||
|
||||
@router.get("/test-connection")
|
||||
async def test_gitea_connection(
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""
|
||||
测试Gitea连接
|
||||
"""
|
||||
success = gitea_service.test_connection()
|
||||
if not success:
|
||||
raise HTTPException(status_code=500, detail="Failed to connect to Gitea server")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Connected to Gitea server successfully"
|
||||
}
|
||||
|
||||
|
||||
@router.get("/repos")
|
||||
async def list_gitea_repositories(
|
||||
owner: Optional[str] = None,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""
|
||||
列出Gitea仓库
|
||||
"""
|
||||
repos = gitea_service.list_repositories(owner)
|
||||
if repos is None:
|
||||
raise HTTPException(status_code=500, detail="Failed to list repositories")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"repositories": repos
|
||||
}
|
||||
|
||||
|
||||
@router.post("/repos/create")
|
||||
async def create_gitea_repository(
|
||||
algorithm_id: str = Body(..., description="算法ID"),
|
||||
algorithm_name: str = Body(..., description="算法名称"),
|
||||
description: str = Body("", description="仓库描述"),
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""
|
||||
创建Gitea仓库
|
||||
"""
|
||||
repo = gitea_service.create_repository(algorithm_id, algorithm_name, description)
|
||||
if not repo:
|
||||
raise HTTPException(status_code=500, detail="Failed to create repository")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"repository": repo
|
||||
}
|
||||
|
||||
|
||||
@router.post("/repos/clone")
|
||||
async def clone_gitea_repository(
|
||||
repo_url: str = Body(..., description="仓库URL"),
|
||||
algorithm_id: str = Body(..., description="算法ID"),
|
||||
branch: str = Body("main", description="分支名称"),
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""
|
||||
克隆Gitea仓库
|
||||
"""
|
||||
success = gitea_service.clone_repository(repo_url, algorithm_id, branch)
|
||||
if not success:
|
||||
# 即使克隆失败,也尝试继续执行,因为我们可能已经初始化了仓库
|
||||
logger.info("Clone failed, but continuing with existing repository setup")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Repository cloned or initialized successfully"
|
||||
}
|
||||
|
||||
|
||||
@router.post("/repos/push")
|
||||
async def push_to_gitea_repository(
|
||||
algorithm_id: str = Body(..., description="算法ID"),
|
||||
message: str = Body("Update code", description="提交消息"),
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""
|
||||
推送代码到Gitea仓库
|
||||
"""
|
||||
success = gitea_service.push_to_repository(algorithm_id, message)
|
||||
if not success:
|
||||
raise HTTPException(status_code=500, detail="Failed to push code")
|
||||
|
||||
# 验证推送是否成功
|
||||
verify_success = gitea_service.verify_push(algorithm_id)
|
||||
if not verify_success:
|
||||
logger.warning(f"Push completed but verification failed for algorithm: {algorithm_id}")
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Code pushed but verification failed",
|
||||
"verified": False
|
||||
}
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Code pushed successfully",
|
||||
"verified": True
|
||||
}
|
||||
|
||||
|
||||
@router.post("/repos/upload", dependencies=[Depends(get_current_active_user)])
|
||||
async def upload_files_to_repository(
|
||||
files: list[UploadFile] = File(..., description="上传的文件列表"),
|
||||
algorithm_id: str = Form(..., description="算法ID")
|
||||
):
|
||||
"""
|
||||
上传文件到仓库(支持大量文件)
|
||||
"""
|
||||
try:
|
||||
logger.info("=== 开始上传文件 ===")
|
||||
logger.info(f"Received {len(files)} files for algorithm: {algorithm_id}")
|
||||
|
||||
# 验证文件数量
|
||||
MAX_FILES = 50000
|
||||
if len(files) > MAX_FILES:
|
||||
logger.error(f"Too many files: {len(files)} (max: {MAX_FILES})")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Too many files. Maximum number of files is {MAX_FILES}."
|
||||
)
|
||||
|
||||
# 创建仓库目录
|
||||
repo_dir = f"/tmp/algorithms/{algorithm_id}"
|
||||
logger.info(f"Repository directory: {repo_dir}")
|
||||
os.makedirs(repo_dir, exist_ok=True)
|
||||
logger.info(f"Created repository directory: {repo_dir}")
|
||||
|
||||
# 保存上传的文件
|
||||
logger.info("=== 保存上传的文件 ===")
|
||||
saved_files = []
|
||||
|
||||
# 分批处理文件,避免内存问题
|
||||
batch_size = 100 # 每批处理100个文件
|
||||
for batch_start in range(0, len(files), batch_size):
|
||||
batch_end = min(batch_start + batch_size, len(files))
|
||||
batch = files[batch_start:batch_end]
|
||||
|
||||
logger.info(f"Processing batch {batch_start//batch_size + 1}: files {batch_start+1} to {batch_end}")
|
||||
|
||||
for i, file in enumerate(batch):
|
||||
# 为了获取文件内容,我们需要读取它
|
||||
file_content = await file.read()
|
||||
|
||||
# 获取文件路径(使用file.filename,它应该包含相对路径)
|
||||
file_path = os.path.join(repo_dir, file.filename)
|
||||
logger.info(f"Processing file {batch_start + i + 1}/{len(files)}: {file.filename}")
|
||||
logger.info(f" Target path: {file_path}")
|
||||
|
||||
# 确保文件所在目录存在
|
||||
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
||||
logger.info(f" Created directory: {os.path.dirname(file_path)}")
|
||||
|
||||
# 保存文件
|
||||
try:
|
||||
with open(file_path, "wb") as f:
|
||||
f.write(file_content)
|
||||
|
||||
file_stats = os.stat(file_path)
|
||||
logger.info(f" File size: {file_stats.st_size} bytes")
|
||||
logger.info(f" ✅ File saved successfully: {file_path}")
|
||||
saved_files.append(file_path)
|
||||
except Exception as file_error:
|
||||
logger.error(f" ❌ Failed to save file {file.filename}: {str(file_error)}")
|
||||
raise
|
||||
|
||||
logger.info(f"=== 文件上传完成 ===")
|
||||
logger.info(f"Successfully saved {len(saved_files)} files to repository")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Files uploaded successfully",
|
||||
"saved_files": saved_files,
|
||||
"total_files": len(files)
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"=== 上传文件失败 ===")
|
||||
logger.error(f"Error: {str(e)}")
|
||||
import traceback
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
raise HTTPException(status_code=500, detail=f"Failed to upload files: {str(e)}")
|
||||
|
||||
|
||||
@router.post("/repos/pull")
|
||||
async def pull_from_gitea_repository(
|
||||
algorithm_id: str = Body(..., description="算法ID"),
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""
|
||||
从Gitea仓库拉取代码
|
||||
"""
|
||||
success = gitea_service.pull_from_repository(algorithm_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=500, detail="Failed to pull code")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Code pulled successfully"
|
||||
}
|
||||
|
||||
|
||||
@router.patch("/repos/update")
|
||||
async def update_gitea_repository(
|
||||
algorithm_id: str = Body(..., description="算法ID"),
|
||||
name: Optional[str] = Body(None, description="新的仓库名称"),
|
||||
description: Optional[str] = Body(None, description="新的仓库描述"),
|
||||
private: Optional[bool] = Body(None, description="是否私有"),
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""
|
||||
更新Gitea仓库信息
|
||||
"""
|
||||
updated_repo = gitea_service.update_repository_info(algorithm_id, name, description, private)
|
||||
if not updated_repo:
|
||||
raise HTTPException(status_code=500, detail="Failed to update repository info")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Repository info updated successfully",
|
||||
"repository": updated_repo
|
||||
}
|
||||
|
||||
|
||||
@router.post("/repos/register")
|
||||
async def register_algorithm_from_repository(
|
||||
repo_owner: str = Body(..., description="仓库所有者"),
|
||||
repo_name: str = Body(..., description="仓库名称"),
|
||||
algorithm_id: str = Body(..., description="算法ID"),
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""
|
||||
从仓库注册算法服务
|
||||
"""
|
||||
success = gitea_service.register_algorithm_from_repo(repo_owner, repo_name, algorithm_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=500, detail="Failed to register algorithm from repository")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Algorithm registered from repository successfully"
|
||||
}
|
||||
|
||||
|
||||
@router.get("/repos/{repo_owner}/{repo_name}")
|
||||
async def get_gitea_repository_info(
|
||||
repo_owner: str,
|
||||
repo_name: str,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""
|
||||
获取仓库信息
|
||||
"""
|
||||
repo = gitea_service.get_repository_info(repo_owner, repo_name)
|
||||
if not repo:
|
||||
raise HTTPException(status_code=404, detail="Repository not found")
|
||||
|
||||
return repo
|
||||
240
backend/app/routes/history.py
Normal file
240
backend/app/routes/history.py
Normal file
@@ -0,0 +1,240 @@
|
||||
"""历史记录管理路由,提供调用历史查询、统计和导出功能"""
|
||||
|
||||
from fastapi import APIRouter, HTTPException, status, Depends
|
||||
from typing import List, Dict, Any, Optional
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
||||
from app.services.history_manager import history_manager
|
||||
from app.models.database import get_db
|
||||
from app.routes.user import get_current_active_user
|
||||
|
||||
router = APIRouter(prefix="/history", tags=["history"])
|
||||
|
||||
|
||||
@router.get("/user-calls")
|
||||
async def get_user_call_history(
|
||||
algorithm_id: Optional[str] = None,
|
||||
start_date: Optional[str] = None,
|
||||
end_date: Optional[str] = None,
|
||||
status: Optional[str] = None,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db = Depends(get_db)
|
||||
):
|
||||
"""获取用户的调用历史"""
|
||||
# 解析日期参数
|
||||
start_dt = None
|
||||
end_dt = None
|
||||
if start_date:
|
||||
try:
|
||||
start_dt = datetime.fromisoformat(start_date.replace('Z', '+00:00'))
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="Invalid start_date format")
|
||||
|
||||
if end_date:
|
||||
try:
|
||||
end_dt = datetime.fromisoformat(end_date.replace('Z', '+00:00'))
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="Invalid end_date format")
|
||||
|
||||
# 普通用户只能查看自己的历史,管理员可以查看所有用户历史
|
||||
user_id = current_user.get("id")
|
||||
if current_user.get("role") == "admin":
|
||||
# 管理员可以指定用户ID,否则查看所有用户
|
||||
user_id = None # 这样会返回所有用户的记录
|
||||
|
||||
history = history_manager.get_user_call_history(
|
||||
db=db,
|
||||
user_id=user_id or current_user.get("id"),
|
||||
algorithm_id=algorithm_id,
|
||||
start_date=start_dt,
|
||||
end_date=end_dt,
|
||||
status=status,
|
||||
skip=skip,
|
||||
limit=limit
|
||||
)
|
||||
|
||||
return {
|
||||
"history": [call.__dict__ for call in history],
|
||||
"count": len(history),
|
||||
"skip": skip,
|
||||
"limit": limit
|
||||
}
|
||||
|
||||
|
||||
@router.get("/algorithm-calls/{algorithm_id}")
|
||||
async def get_algorithm_call_history(
|
||||
algorithm_id: str,
|
||||
user_id: Optional[str] = None,
|
||||
start_date: Optional[str] = None,
|
||||
end_date: Optional[str] = None,
|
||||
status: Optional[str] = None,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db = Depends(get_db)
|
||||
):
|
||||
"""获取特定算法的调用历史"""
|
||||
# 验证权限:用户必须有权访问该算法
|
||||
# 在实际实现中,这里应该检查用户是否有权访问该算法
|
||||
# 为简化,我们只检查是否为管理员或查看自己的记录
|
||||
|
||||
# 解析日期参数
|
||||
start_dt = None
|
||||
end_dt = None
|
||||
if start_date:
|
||||
try:
|
||||
start_dt = datetime.fromisoformat(start_date.replace('Z', '+00:00'))
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="Invalid start_date format")
|
||||
|
||||
if end_date:
|
||||
try:
|
||||
end_dt = datetime.fromisoformat(end_date.replace('Z', '+00:00'))
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="Invalid end_date format")
|
||||
|
||||
history = history_manager.get_algorithm_call_history(
|
||||
db=db,
|
||||
algorithm_id=algorithm_id,
|
||||
user_id=user_id,
|
||||
start_date=start_dt,
|
||||
end_date=end_dt,
|
||||
status=status,
|
||||
skip=skip,
|
||||
limit=limit
|
||||
)
|
||||
|
||||
return {
|
||||
"history": [call.__dict__ for call in history],
|
||||
"count": len(history),
|
||||
"skip": skip,
|
||||
"limit": limit
|
||||
}
|
||||
|
||||
|
||||
@router.get("/statistics")
|
||||
async def get_call_statistics(
|
||||
user_id: Optional[str] = None,
|
||||
algorithm_id: Optional[str] = None,
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db = Depends(get_db)
|
||||
):
|
||||
"""获取调用统计信息"""
|
||||
# 权限检查
|
||||
if current_user.get("role") != "admin":
|
||||
# 普通用户只能查看自己的统计
|
||||
if user_id and user_id != current_user.get("id"):
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
user_id = current_user.get("id")
|
||||
|
||||
stats = history_manager.get_call_statistics(
|
||||
db=db,
|
||||
user_id=user_id,
|
||||
algorithm_id=algorithm_id
|
||||
)
|
||||
|
||||
return stats
|
||||
|
||||
|
||||
@router.post("/compare")
|
||||
async def get_comparison_data(
|
||||
call_ids: List[str],
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db = Depends(get_db)
|
||||
):
|
||||
"""获取用于对比的历史数据"""
|
||||
# 权限检查:用户只能对比自己的调用记录
|
||||
# 获取调用记录
|
||||
calls = db.query(AlgorithmCall).filter(AlgorithmCall.id.in_(call_ids)).all()
|
||||
|
||||
# 检查权限:用户只能对比自己的记录
|
||||
for call in calls:
|
||||
if call.user_id != current_user.get("id") and current_user.get("role") != "admin":
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions to access call data")
|
||||
|
||||
comparison_data = history_manager.get_comparison_data(db, call_ids)
|
||||
|
||||
return {
|
||||
"comparison_data": comparison_data,
|
||||
"count": len(comparison_data)
|
||||
}
|
||||
|
||||
|
||||
@router.get("/export")
|
||||
async def export_history(
|
||||
algorithm_id: Optional[str] = None,
|
||||
start_date: Optional[str] = None,
|
||||
end_date: Optional[str] = None,
|
||||
format_type: str = "json",
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db = Depends(get_db)
|
||||
):
|
||||
"""导出历史记录"""
|
||||
# 解析日期参数
|
||||
start_dt = None
|
||||
end_dt = None
|
||||
if start_date:
|
||||
try:
|
||||
start_dt = datetime.fromisoformat(start_date.replace('Z', '+00:00'))
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="Invalid start_date format")
|
||||
|
||||
if end_date:
|
||||
try:
|
||||
end_dt = datetime.fromisoformat(end_date.replace('Z', '+00:00'))
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="Invalid end_date format")
|
||||
|
||||
file_path = history_manager.export_history(
|
||||
db=db,
|
||||
user_id=current_user.get("id"),
|
||||
algorithm_id=algorithm_id,
|
||||
start_date=start_dt,
|
||||
end_date=end_dt,
|
||||
format_type=format_type
|
||||
)
|
||||
|
||||
if file_path:
|
||||
return {
|
||||
"success": True,
|
||||
"file_path": file_path,
|
||||
"download_url": f"/api/files/download/{file_path}",
|
||||
"message": "History exported successfully"
|
||||
}
|
||||
else:
|
||||
raise HTTPException(status_code=500, detail="Failed to export history")
|
||||
|
||||
|
||||
@router.delete("/cleanup")
|
||||
async def cleanup_old_history(
|
||||
days_old: int,
|
||||
algorithm_id: Optional[str] = None,
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db = Depends(get_db)
|
||||
):
|
||||
"""清理旧的历史记录"""
|
||||
# 只有管理员可以清理历史记录
|
||||
if current_user.get("role") != "admin":
|
||||
raise HTTPException(status_code=403, detail="Only administrators can clean up history")
|
||||
|
||||
# 确保天数为正数
|
||||
if days_old <= 0:
|
||||
raise HTTPException(status_code=400, detail="days_old must be positive")
|
||||
|
||||
deleted_count = history_manager.delete_old_history(
|
||||
db=db,
|
||||
days_old=days_old,
|
||||
algorithm_id=algorithm_id
|
||||
)
|
||||
|
||||
return {
|
||||
"message": f"Cleaned up {deleted_count} old history records",
|
||||
"deleted_count": deleted_count
|
||||
}
|
||||
|
||||
|
||||
# 导入需要的模型
|
||||
from app.models.models import AlgorithmCall
|
||||
345
backend/app/routes/monitoring.py
Normal file
345
backend/app/routes/monitoring.py
Normal file
@@ -0,0 +1,345 @@
|
||||
"""监控与日志路由,提供系统监控、指标收集和日志查询功能"""
|
||||
|
||||
from fastapi import APIRouter, HTTPException, status, Depends
|
||||
from typing import List, Dict, Any, Optional
|
||||
from datetime import datetime, timedelta
|
||||
import json
|
||||
|
||||
from app.services.monitoring import monitoring_service
|
||||
from app.utils.logger import structured_logger, log_query
|
||||
from app.models.database import get_db
|
||||
from app.routes.user import get_current_active_user
|
||||
|
||||
router = APIRouter(prefix="/monitoring", tags=["monitoring"])
|
||||
|
||||
|
||||
@router.get("/health")
|
||||
async def get_system_health():
|
||||
"""获取系统健康状况"""
|
||||
health = monitoring_service.get_system_health()
|
||||
return health
|
||||
|
||||
|
||||
@router.get("/dashboard")
|
||||
async def get_dashboard_data(
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db = Depends(get_db)
|
||||
):
|
||||
"""获取仪表板数据"""
|
||||
# 只有管理员可以访问仪表板
|
||||
if current_user.get("role") not in ["admin", "manager"]:
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
dashboard_data = monitoring_service.get_dashboard_data(db)
|
||||
return dashboard_data
|
||||
|
||||
|
||||
@router.get("/metrics/system")
|
||||
async def get_system_metrics(
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取系统指标"""
|
||||
if current_user.get("role") not in ["admin", "manager"]:
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
from app.services.monitoring import MetricsCollector
|
||||
collector = MetricsCollector()
|
||||
metrics = collector.collect_system_metrics()
|
||||
return metrics
|
||||
|
||||
|
||||
@router.get("/metrics/business")
|
||||
async def get_business_metrics(
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db = Depends(get_db)
|
||||
):
|
||||
"""获取业务指标"""
|
||||
if current_user.get("role") not in ["admin", "manager"]:
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
from app.services.monitoring import MetricsCollector
|
||||
collector = MetricsCollector()
|
||||
metrics = collector.collect_business_metrics(db)
|
||||
return metrics
|
||||
|
||||
|
||||
@router.get("/metrics/history")
|
||||
async def get_metrics_history(
|
||||
metric_type: str = "system",
|
||||
limit: int = 100,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取指标历史"""
|
||||
if current_user.get("role") not in ["admin", "manager"]:
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
if metric_type not in ["system", "business"]:
|
||||
raise HTTPException(status_code=400, detail="Invalid metric type. Use 'system' or 'business'")
|
||||
|
||||
from app.services.monitoring import MetricsCollector
|
||||
collector = MetricsCollector()
|
||||
history = collector.get_metric_history(metric_type, limit)
|
||||
return {"history": history}
|
||||
|
||||
|
||||
@router.get("/alerts/active")
|
||||
async def get_active_alerts(
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取当前激活的告警"""
|
||||
if current_user.get("role") not in ["admin", "manager"]:
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
active_alerts = monitoring_service.alert_manager.get_active_alerts()
|
||||
return {"active_alerts": active_alerts}
|
||||
|
||||
|
||||
@router.get("/alerts/history")
|
||||
async def get_alert_history(
|
||||
limit: int = 100,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取告警历史"""
|
||||
if current_user.get("role") not in ["admin", "manager"]:
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
history = monitoring_service.alert_manager.get_alert_history(limit)
|
||||
return {"alert_history": history}
|
||||
|
||||
|
||||
@router.post("/monitoring/start")
|
||||
async def start_monitoring(
|
||||
interval: int = 60,
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db = Depends(get_db)
|
||||
):
|
||||
"""启动监控"""
|
||||
if current_user.get("role") not in ["admin", "manager"]:
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
# 注意:在实际应用中,我们不会在这里启动一个长时间运行的协程
|
||||
# 这通常会在应用启动时完成
|
||||
# 这里仅作为示例返回确认信息
|
||||
return {
|
||||
"message": "Monitoring started",
|
||||
"interval": interval,
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
|
||||
@router.post("/monitoring/stop")
|
||||
async def stop_monitoring(
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""停止监控"""
|
||||
if current_user.get("role") not in ["admin", "manager"]:
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
await monitoring_service.stop_monitoring()
|
||||
return {
|
||||
"message": "Monitoring stopped",
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
|
||||
@router.post("/logs/event")
|
||||
async def log_custom_event(
|
||||
event_type: str,
|
||||
user_id: Optional[str] = None,
|
||||
algorithm_id: Optional[str] = None,
|
||||
extra_data: Dict[str, Any] = {},
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""记录自定义事件日志"""
|
||||
# 普通用户只能记录自己的事件
|
||||
if current_user.get("role") not in ["admin", "manager"]:
|
||||
if user_id and user_id != current_user.get("id"):
|
||||
raise HTTPException(status_code=403, detail="Cannot log events for other users")
|
||||
user_id = current_user.get("id")
|
||||
|
||||
structured_logger.log_event(
|
||||
event_type=event_type,
|
||||
user_id=user_id,
|
||||
algorithm_id=algorithm_id,
|
||||
extra_data=extra_data
|
||||
)
|
||||
|
||||
return {
|
||||
"message": "Event logged successfully",
|
||||
"event_type": event_type,
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
|
||||
@router.post("/logs/api-call")
|
||||
async def log_api_call(
|
||||
user_id: str,
|
||||
algorithm_id: str,
|
||||
version_id: str,
|
||||
input_size: int,
|
||||
response_time: float,
|
||||
success: bool,
|
||||
error_msg: Optional[str] = None,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""记录API调用日志"""
|
||||
# 管理员或用户自己可以记录日志
|
||||
if current_user.get("role") not in ["admin", "manager"]:
|
||||
if user_id != current_user.get("id"):
|
||||
raise HTTPException(status_code=403, detail="Cannot log API calls for other users")
|
||||
|
||||
structured_logger.log_api_call(
|
||||
user_id=user_id,
|
||||
algorithm_id=algorithm_id,
|
||||
version_id=version_id,
|
||||
input_size=input_size,
|
||||
response_time=response_time,
|
||||
success=success,
|
||||
error_msg=error_msg
|
||||
)
|
||||
|
||||
return {
|
||||
"message": "API call logged successfully",
|
||||
"success": success,
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
|
||||
@router.get("/logs/search")
|
||||
async def search_logs(
|
||||
start_date: Optional[str] = None,
|
||||
end_date: Optional[str] = None,
|
||||
event_types: Optional[str] = None, # 逗号分隔的事件类型
|
||||
user_ids: Optional[str] = None, # 逗号分隔的用户ID
|
||||
algorithm_ids: Optional[str] = None, # 逗号分隔的算法ID
|
||||
log_levels: Optional[str] = None, # 逗号分隔的日志级别
|
||||
limit: int = 100,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""搜索日志"""
|
||||
# 普通用户只能搜索自己的日志
|
||||
if current_user.get("role") not in ["admin", "manager"]:
|
||||
# 如果指定了其他用户ID,则只允许查看自己的
|
||||
if user_ids:
|
||||
user_id_list = user_ids.split(',')
|
||||
if current_user.get("id") not in user_id_list:
|
||||
raise HTTPException(status_code=403, detail="Cannot search logs for other users")
|
||||
else:
|
||||
user_ids = current_user.get("id")
|
||||
|
||||
# 解析日期
|
||||
start_dt = None
|
||||
end_dt = None
|
||||
if start_date:
|
||||
try:
|
||||
start_dt = datetime.fromisoformat(start_date.replace('Z', '+00:00'))
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="Invalid start_date format")
|
||||
|
||||
if end_date:
|
||||
try:
|
||||
end_dt = datetime.fromisoformat(end_date.replace('Z', '+00:00'))
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="Invalid end_date format")
|
||||
|
||||
# 解析数组参数
|
||||
event_type_list = event_types.split(',') if event_types else None
|
||||
user_id_list = user_ids.split(',') if user_ids else None
|
||||
algorithm_id_list = algorithm_ids.split(',') if algorithm_ids else None
|
||||
log_level_list = log_levels.split(',') if log_levels else None
|
||||
|
||||
# 执行搜索
|
||||
results = log_query.search_logs(
|
||||
start_date=start_dt,
|
||||
end_date=end_dt,
|
||||
event_types=event_type_list,
|
||||
user_ids=user_id_list,
|
||||
algorithm_ids=algorithm_id_list,
|
||||
log_levels=log_level_list,
|
||||
limit=limit
|
||||
)
|
||||
|
||||
return {
|
||||
"logs": results,
|
||||
"count": len(results),
|
||||
"limit": limit
|
||||
}
|
||||
|
||||
|
||||
@router.get("/logs/stats")
|
||||
async def get_log_stats(
|
||||
days: int = 7,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取日志统计信息"""
|
||||
if current_user.get("role") not in ["admin", "manager"]:
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
stats = log_query.get_log_stats(days=days)
|
||||
return stats
|
||||
|
||||
|
||||
@router.get("/performance/algorithm/{algorithm_id}")
|
||||
async def get_algorithm_performance(
|
||||
algorithm_id: str,
|
||||
days: int = 7,
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db = Depends(get_db)
|
||||
):
|
||||
"""获取算法性能指标"""
|
||||
# 用户只能查看自己有权访问的算法
|
||||
if current_user.get("role") not in ["admin", "manager"]:
|
||||
# 这里应该检查用户是否有权访问该算法
|
||||
# 简单起见,我们假设用户可以查看任何算法
|
||||
pass
|
||||
|
||||
from sqlalchemy import func
|
||||
from app.models.models import AlgorithmCall
|
||||
|
||||
# 计算性能指标
|
||||
start_date = datetime.utcnow() - timedelta(days=days)
|
||||
|
||||
# 总调用次数
|
||||
total_calls = db.query(func.count(AlgorithmCall.id)).filter(
|
||||
AlgorithmCall.algorithm_id == algorithm_id,
|
||||
AlgorithmCall.created_at >= start_date
|
||||
).scalar()
|
||||
|
||||
# 成功调用次数
|
||||
success_calls = db.query(func.count(AlgorithmCall.id)).filter(
|
||||
AlgorithmCall.algorithm_id == algorithm_id,
|
||||
AlgorithmCall.status == 'success',
|
||||
AlgorithmCall.created_at >= start_date
|
||||
).scalar()
|
||||
|
||||
# 平均响应时间
|
||||
avg_response_time = db.query(func.avg(AlgorithmCall.response_time)).filter(
|
||||
AlgorithmCall.algorithm_id == algorithm_id,
|
||||
AlgorithmCall.response_time.isnot(None),
|
||||
AlgorithmCall.created_at >= start_date
|
||||
).scalar()
|
||||
|
||||
# 按状态分组
|
||||
status_counts = db.query(
|
||||
AlgorithmCall.status,
|
||||
func.count(AlgorithmCall.id)
|
||||
).filter(
|
||||
AlgorithmCall.algorithm_id == algorithm_id,
|
||||
AlgorithmCall.created_at >= start_date
|
||||
).group_by(AlgorithmCall.status).all()
|
||||
|
||||
status_dict = {status: count for status, count in status_counts}
|
||||
|
||||
success_rate = (success_calls / total_calls * 100) if total_calls > 0 else 0
|
||||
|
||||
return {
|
||||
"algorithm_id": algorithm_id,
|
||||
"period_days": days,
|
||||
"total_calls": total_calls,
|
||||
"success_calls": success_calls,
|
||||
"failed_calls": total_calls - success_calls,
|
||||
"success_rate": round(success_rate, 2),
|
||||
"average_response_time": round(avg_response_time, 3) if avg_response_time else None,
|
||||
"status_distribution": status_dict,
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
42
backend/app/routes/openai.py
Normal file
42
backend/app/routes/openai.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from typing import Optional
|
||||
|
||||
from app.utils.openai import openai_client
|
||||
from app.routes.user import get_current_active_user
|
||||
|
||||
# 创建路由器
|
||||
router = APIRouter(prefix="/openai", tags=["openai"])
|
||||
|
||||
|
||||
@router.post("/generate-data", response_model=dict)
|
||||
async def generate_simulation_data(
|
||||
prompt: str,
|
||||
data_type: str = "text",
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""生成仿真输入数据"""
|
||||
# 验证数据类型
|
||||
valid_types = ["text", "image", "structured"]
|
||||
if data_type not in valid_types:
|
||||
raise HTTPException(status_code=400, detail=f"Invalid data type. Valid types are: {', '.join(valid_types)}")
|
||||
|
||||
# 生成数据
|
||||
result = openai_client.generate_simulation_data(prompt, data_type)
|
||||
if not result:
|
||||
raise HTTPException(status_code=500, detail="Failed to generate data from OpenAI")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.post("/describe-image", response_model=dict)
|
||||
async def generate_image_description(
|
||||
image_url: str,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""生成图片描述"""
|
||||
# 生成图片描述
|
||||
description = openai_client.generate_image_description(image_url)
|
||||
if not description:
|
||||
raise HTTPException(status_code=500, detail="Failed to generate image description from OpenAI")
|
||||
|
||||
return {"description": description}
|
||||
264
backend/app/routes/permissions.py
Normal file
264
backend/app/routes/permissions.py
Normal file
@@ -0,0 +1,264 @@
|
||||
"""权限管理路由,提供算法访问权限和用户权限管理功能"""
|
||||
|
||||
from fastapi import APIRouter, HTTPException, status, Depends
|
||||
from typing import List, Dict, Any, Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.services.permission import (
|
||||
permission_manager, rbac_manager,
|
||||
AccessLevel, PermissionType
|
||||
)
|
||||
from app.models.database import get_db
|
||||
from app.routes.user import get_current_active_user
|
||||
|
||||
router = APIRouter(prefix="/permissions", tags=["permissions"])
|
||||
|
||||
|
||||
class GrantPermissionRequest(BaseModel):
|
||||
"""授予权限请求"""
|
||||
user_id: str
|
||||
algorithm_id: str
|
||||
access_level: str # 使用字符串,稍后转换为AccessLevel
|
||||
|
||||
|
||||
class CheckPermissionRequest(BaseModel):
|
||||
"""检查权限请求"""
|
||||
algorithm_id: str
|
||||
permission_type: str # 使用字符串,稍后转换为PermissionType
|
||||
|
||||
|
||||
class RevokePermissionRequest(BaseModel):
|
||||
"""撤销权限请求"""
|
||||
user_id: str
|
||||
algorithm_id: str
|
||||
|
||||
|
||||
@router.post("/grant")
|
||||
async def grant_permission(
|
||||
request: GrantPermissionRequest,
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db = Depends(get_db)
|
||||
):
|
||||
"""授予用户对算法的权限"""
|
||||
# 只有管理员和经理可以授予权限
|
||||
if current_user.get("role") not in ["admin", "manager"]:
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions to grant permissions")
|
||||
|
||||
# 验证访问级别
|
||||
try:
|
||||
access_level = AccessLevel(request.access_level)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail=f"Invalid access level. Valid levels: {[level.value for level in AccessLevel]}")
|
||||
|
||||
success = permission_manager.grant_permission(
|
||||
db, current_user.get("id"), request.user_id,
|
||||
request.algorithm_id, access_level
|
||||
)
|
||||
|
||||
if success:
|
||||
return {
|
||||
"message": "Permission granted successfully",
|
||||
"user_id": request.user_id,
|
||||
"algorithm_id": request.algorithm_id,
|
||||
"access_level": request.access_level
|
||||
}
|
||||
else:
|
||||
raise HTTPException(status_code=500, detail="Failed to grant permission")
|
||||
|
||||
|
||||
@router.post("/revoke")
|
||||
async def revoke_permission(
|
||||
request: RevokePermissionRequest,
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db = Depends(get_db)
|
||||
):
|
||||
"""撤销用户对算法的权限"""
|
||||
# 只有管理员和经理可以撤销权限
|
||||
if current_user.get("role") not in ["admin", "manager"]:
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions to revoke permissions")
|
||||
|
||||
success = permission_manager.revoke_permission(
|
||||
db, current_user.get("id"), request.user_id, request.algorithm_id
|
||||
)
|
||||
|
||||
if success:
|
||||
return {
|
||||
"message": "Permission revoked successfully",
|
||||
"user_id": request.user_id,
|
||||
"algorithm_id": request.algorithm_id
|
||||
}
|
||||
else:
|
||||
raise HTTPException(status_code=500, detail="Failed to revoke permission")
|
||||
|
||||
|
||||
@router.post("/check")
|
||||
async def check_permission(
|
||||
request: CheckPermissionRequest,
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db = Depends(get_db)
|
||||
):
|
||||
"""检查用户对算法的权限"""
|
||||
# 验证权限类型
|
||||
try:
|
||||
permission_type = PermissionType(request.permission_type)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail=f"Invalid permission type. Valid types: {[ptype.value for ptype in PermissionType]}")
|
||||
|
||||
has_permission = permission_manager.check_algorithm_access(
|
||||
db, current_user.get("id"), request.algorithm_id, permission_type
|
||||
)
|
||||
|
||||
return {
|
||||
"has_permission": has_permission,
|
||||
"user_id": current_user.get("id"),
|
||||
"algorithm_id": request.algorithm_id,
|
||||
"permission_type": request.permission_type
|
||||
}
|
||||
|
||||
|
||||
@router.get("/user/{user_id}")
|
||||
async def get_user_permissions(
|
||||
user_id: str,
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db = Depends(get_db)
|
||||
):
|
||||
"""获取用户的权限列表"""
|
||||
# 用户只能查看自己的权限,管理员可以查看任何用户权限
|
||||
if current_user.get("role") not in ["admin", "manager"]:
|
||||
if user_id != current_user.get("id"):
|
||||
raise HTTPException(status_code=403, detail="Cannot view permissions for other users")
|
||||
|
||||
permissions = permission_manager.get_user_permissions(db, user_id)
|
||||
|
||||
return {
|
||||
"user_id": user_id,
|
||||
"permissions": permissions,
|
||||
"count": len(permissions)
|
||||
}
|
||||
|
||||
|
||||
@router.get("/algorithm/{algorithm_id}")
|
||||
async def get_algorithm_permissions(
|
||||
algorithm_id: str,
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db = Depends(get_db)
|
||||
):
|
||||
"""获取算法的权限分配情况"""
|
||||
# 检查用户是否有权限查看算法权限
|
||||
can_read = permission_manager.check_algorithm_access(
|
||||
db, current_user.get("id"), algorithm_id, PermissionType.READ
|
||||
)
|
||||
|
||||
if not can_read and current_user.get("role") not in ["admin", "manager"]:
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions to view algorithm permissions")
|
||||
|
||||
permissions = permission_manager.get_algorithm_permissions(db, algorithm_id)
|
||||
|
||||
return {
|
||||
"algorithm_id": algorithm_id,
|
||||
"permissions": permissions,
|
||||
"count": len(permissions)
|
||||
}
|
||||
|
||||
|
||||
@router.get("/role/{role_name}")
|
||||
async def get_role_permissions(
|
||||
role_name: str,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取角色的权限列表"""
|
||||
# 所有用户都可以查看角色权限
|
||||
permissions = rbac_manager.get_role_permissions(role_name)
|
||||
|
||||
if not permissions:
|
||||
raise HTTPException(status_code=404, detail="Role not found")
|
||||
|
||||
return {
|
||||
"role": role_name,
|
||||
"permissions": [perm.value for perm in permissions]
|
||||
}
|
||||
|
||||
|
||||
@router.get("/validate-operation")
|
||||
async def validate_user_algorithm_operation(
|
||||
algorithm_id: str,
|
||||
operation: str,
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db = Depends(get_db)
|
||||
):
|
||||
"""验证用户对算法的操作权限"""
|
||||
is_valid = permission_manager.validate_user_algorithm_operation(
|
||||
db, current_user.get("id"), algorithm_id, operation
|
||||
)
|
||||
|
||||
return {
|
||||
"user_id": current_user.get("id"),
|
||||
"algorithm_id": algorithm_id,
|
||||
"operation": operation,
|
||||
"has_permission": is_valid
|
||||
}
|
||||
|
||||
|
||||
@router.get("/my-permissions")
|
||||
async def get_my_permissions(
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db = Depends(get_db)
|
||||
):
|
||||
"""获取当前用户的权限"""
|
||||
permissions = permission_manager.get_user_permissions(db, current_user.get("id"))
|
||||
|
||||
return {
|
||||
"user_id": current_user.get("id"),
|
||||
"username": current_user.get("username"),
|
||||
"role": current_user.get("role"),
|
||||
"permissions": permissions,
|
||||
"count": len(permissions)
|
||||
}
|
||||
|
||||
|
||||
@router.get("/user-role-permissions/{user_id}")
|
||||
async def get_user_role_based_permissions(
|
||||
user_id: str,
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db = Depends(get_db)
|
||||
):
|
||||
"""获取用户的基于角色的权限(而非具体算法权限)"""
|
||||
# 用户只能查看自己的权限,管理员可以查看任何用户权限
|
||||
if current_user.get("role") not in ["admin", "manager"]:
|
||||
if user_id != current_user.get("id"):
|
||||
raise HTTPException(status_code=403, detail="Cannot view permissions for other users")
|
||||
|
||||
# 获取用户角色
|
||||
from app.models.models import User
|
||||
user = db.query(User).filter(User.id == user_id).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
role_permissions = rbac_manager.get_role_permissions(user.role)
|
||||
|
||||
return {
|
||||
"user_id": user_id,
|
||||
"role": user.role,
|
||||
"role_permissions": [perm.value for perm in role_permissions]
|
||||
}
|
||||
|
||||
|
||||
@router.get("/check-api-key-access")
|
||||
async def check_api_key_access(
|
||||
api_key_value: str,
|
||||
algorithm_id: str,
|
||||
current_user: dict = Depends(get_current_active_user),
|
||||
db = Depends(get_db)
|
||||
):
|
||||
"""检查API密钥对算法的访问权限"""
|
||||
# 只有管理员可以检查任意API密钥的权限
|
||||
if current_user.get("role") != "admin":
|
||||
raise HTTPException(status_code=403, detail="Only admins can check API key access")
|
||||
|
||||
has_access = permission_manager.check_api_key_access(db, api_key_value, algorithm_id)
|
||||
|
||||
return {
|
||||
"api_key_valid": True, # 如果到达这里,说明API密钥存在且活跃
|
||||
"has_algorithm_access": has_access,
|
||||
"algorithm_id": algorithm_id
|
||||
}
|
||||
280
backend/app/routes/repositories.py
Normal file
280
backend/app/routes/repositories.py
Normal file
@@ -0,0 +1,280 @@
|
||||
"""算法仓库管理路由,提供仓库添加、列表、删除等功能"""
|
||||
|
||||
from fastapi import APIRouter, HTTPException, status, Depends
|
||||
from typing import List, Dict, Any, Optional
|
||||
from pydantic import BaseModel
|
||||
import uuid
|
||||
|
||||
from app.models.models import AlgorithmRepository
|
||||
from app.models.database import SessionLocal
|
||||
from app.routes.user import get_current_active_user
|
||||
from app.gitea.service import gitea_service
|
||||
|
||||
router = APIRouter(prefix="/repositories", tags=["repositories"])
|
||||
|
||||
|
||||
class CreateRepositoryRequest(BaseModel):
|
||||
"""创建仓库请求"""
|
||||
name: str
|
||||
description: str
|
||||
type: str = "code"
|
||||
repo_url: str
|
||||
branch: str = "main"
|
||||
local_path: str = ""
|
||||
algorithm_id: Optional[str] = None
|
||||
|
||||
|
||||
class UpdateRepositoryRequest(BaseModel):
|
||||
"""更新仓库请求"""
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
repo_url: Optional[str] = None
|
||||
branch: Optional[str] = None
|
||||
local_path: Optional[str] = None
|
||||
algorithm_id: Optional[str] = None
|
||||
|
||||
|
||||
@router.post("", status_code=status.HTTP_201_CREATED)
|
||||
async def create_repository(
|
||||
request: CreateRepositoryRequest,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""创建算法仓库"""
|
||||
# 检查用户权限
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 生成唯一ID
|
||||
repo_id = str(uuid.uuid4())
|
||||
|
||||
# 创建仓库实例
|
||||
repo = AlgorithmRepository(
|
||||
id=repo_id,
|
||||
name=request.name,
|
||||
description=request.description,
|
||||
type=request.type,
|
||||
repo_url=request.repo_url,
|
||||
branch=request.branch,
|
||||
local_path=request.local_path,
|
||||
algorithm_id=request.algorithm_id
|
||||
)
|
||||
|
||||
# 保存到数据库
|
||||
db.add(repo)
|
||||
db.commit()
|
||||
db.refresh(repo)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Repository created successfully",
|
||||
"repository": {
|
||||
"id": repo.id,
|
||||
"name": repo.name,
|
||||
"description": repo.description,
|
||||
"type": repo.type,
|
||||
"repo_url": repo.repo_url,
|
||||
"branch": repo.branch,
|
||||
"local_path": repo.local_path,
|
||||
"algorithm_id": repo.algorithm_id,
|
||||
"status": repo.status,
|
||||
"created_at": repo.created_at,
|
||||
"updated_at": repo.updated_at
|
||||
}
|
||||
}
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_repositories(
|
||||
algorithm_id: Optional[str] = None,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取算法仓库列表"""
|
||||
# 检查用户权限
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 查询仓库列表
|
||||
query = db.query(AlgorithmRepository)
|
||||
|
||||
# 如果指定了算法ID,只返回该算法的仓库
|
||||
if algorithm_id:
|
||||
query = query.filter(AlgorithmRepository.algorithm_id == algorithm_id)
|
||||
|
||||
repos = query.all()
|
||||
|
||||
# 转换为字典列表
|
||||
repo_list = []
|
||||
for repo in repos:
|
||||
repo_list.append({
|
||||
"id": repo.id,
|
||||
"name": repo.name,
|
||||
"description": repo.description,
|
||||
"type": repo.type,
|
||||
"repo_url": repo.repo_url,
|
||||
"branch": repo.branch,
|
||||
"local_path": repo.local_path,
|
||||
"algorithm_id": repo.algorithm_id,
|
||||
"status": repo.status,
|
||||
"created_at": repo.created_at,
|
||||
"updated_at": repo.updated_at
|
||||
})
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"repositories": repo_list
|
||||
}
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.get("/{repo_id}")
|
||||
async def get_repository(
|
||||
repo_id: str,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取单个算法仓库"""
|
||||
# 检查用户权限
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 查询仓库
|
||||
repo = db.query(AlgorithmRepository).filter(AlgorithmRepository.id == repo_id).first()
|
||||
|
||||
if not repo:
|
||||
raise HTTPException(status_code=404, detail="Repository not found")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"repository": {
|
||||
"id": repo.id,
|
||||
"name": repo.name,
|
||||
"description": repo.description,
|
||||
"type": repo.type,
|
||||
"repo_url": repo.repo_url,
|
||||
"branch": repo.branch,
|
||||
"local_path": repo.local_path,
|
||||
"algorithm_id": repo.algorithm_id,
|
||||
"status": repo.status,
|
||||
"created_at": repo.created_at,
|
||||
"updated_at": repo.updated_at
|
||||
}
|
||||
}
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.put("/{repo_id}")
|
||||
async def update_repository(
|
||||
repo_id: str,
|
||||
request: UpdateRepositoryRequest,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""更新算法仓库"""
|
||||
# 检查用户权限
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 查询仓库
|
||||
repo = db.query(AlgorithmRepository).filter(AlgorithmRepository.id == repo_id).first()
|
||||
|
||||
if not repo:
|
||||
raise HTTPException(status_code=404, detail="Repository not found")
|
||||
|
||||
# 更新仓库信息
|
||||
if request.name is not None:
|
||||
repo.name = request.name
|
||||
if request.description is not None:
|
||||
repo.description = request.description
|
||||
if request.type is not None:
|
||||
repo.type = request.type
|
||||
if request.repo_url is not None:
|
||||
repo.repo_url = request.repo_url
|
||||
if request.branch is not None:
|
||||
repo.branch = request.branch
|
||||
if request.local_path is not None:
|
||||
repo.local_path = request.local_path
|
||||
if request.algorithm_id is not None:
|
||||
repo.algorithm_id = request.algorithm_id
|
||||
|
||||
# 保存到数据库
|
||||
db.commit()
|
||||
db.refresh(repo)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Repository updated successfully",
|
||||
"repository": {
|
||||
"id": repo.id,
|
||||
"name": repo.name,
|
||||
"description": repo.description,
|
||||
"type": repo.type,
|
||||
"repo_url": repo.repo_url,
|
||||
"branch": repo.branch,
|
||||
"local_path": repo.local_path,
|
||||
"algorithm_id": repo.algorithm_id,
|
||||
"status": repo.status,
|
||||
"created_at": repo.created_at,
|
||||
"updated_at": repo.updated_at
|
||||
}
|
||||
}
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.delete("/{repo_id}")
|
||||
async def delete_repository(
|
||||
repo_id: str,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""删除算法仓库"""
|
||||
# 检查用户权限
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 查询仓库
|
||||
repo = db.query(AlgorithmRepository).filter(AlgorithmRepository.id == repo_id).first()
|
||||
|
||||
if not repo:
|
||||
raise HTTPException(status_code=404, detail="Repository not found")
|
||||
|
||||
# 先删除Gitea仓库
|
||||
gitea_deleted = False
|
||||
if repo.repo_url:
|
||||
# 从repo_url中提取仓库名称
|
||||
import os
|
||||
repo_name = os.path.basename(repo.repo_url).replace('.git', '')
|
||||
gitea_deleted = gitea_service.delete_repository(repo_name)
|
||||
if gitea_deleted:
|
||||
print(f"Gitea repository deleted successfully: {repo_name}")
|
||||
else:
|
||||
print(f"Failed to delete Gitea repository: {repo_name}")
|
||||
|
||||
# 删除系统仓库数据
|
||||
db.delete(repo)
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Repository deleted successfully",
|
||||
"gitea_deleted": gitea_deleted
|
||||
}
|
||||
finally:
|
||||
db.close()
|
||||
569
backend/app/routes/services.py
Normal file
569
backend/app/routes/services.py
Normal file
@@ -0,0 +1,569 @@
|
||||
"""算法服务管理路由,提供服务注册、管理等功能"""
|
||||
|
||||
from fastapi import APIRouter, HTTPException, status, Depends
|
||||
from typing import List, Dict, Any, Optional
|
||||
from pydantic import BaseModel
|
||||
import uuid
|
||||
import os
|
||||
|
||||
from app.models.models import AlgorithmService
|
||||
from app.models.database import SessionLocal
|
||||
from app.routes.user import get_current_active_user
|
||||
from app.services.project_analyzer import ProjectAnalyzer
|
||||
from app.services.service_generator import ServiceGenerator
|
||||
from app.services.service_orchestrator import ServiceOrchestrator
|
||||
|
||||
router = APIRouter(prefix="/services", tags=["services"])
|
||||
|
||||
|
||||
class RegisterServiceRequest(BaseModel):
|
||||
"""注册服务请求"""
|
||||
repository_id: str
|
||||
name: str
|
||||
version: str = "1.0.0"
|
||||
service_type: str = "http"
|
||||
host: str = "0.0.0.0"
|
||||
port: int = 8000
|
||||
timeout: int = 30
|
||||
health_check_path: str = "/health"
|
||||
environment: Dict[str, str] = {}
|
||||
|
||||
|
||||
class ServiceResponse(BaseModel):
|
||||
"""服务响应"""
|
||||
id: str
|
||||
service_id: str
|
||||
name: str
|
||||
algorithm_name: str
|
||||
version: str
|
||||
host: str
|
||||
port: int
|
||||
api_url: str
|
||||
status: str
|
||||
created_at: str
|
||||
updated_at: str
|
||||
|
||||
|
||||
class ServiceListResponse(BaseModel):
|
||||
"""服务列表响应"""
|
||||
success: bool
|
||||
services: List[ServiceResponse]
|
||||
|
||||
|
||||
class ServiceDetailResponse(BaseModel):
|
||||
"""服务详情响应"""
|
||||
success: bool
|
||||
service: ServiceResponse
|
||||
|
||||
|
||||
class ServiceOperationResponse(BaseModel):
|
||||
"""服务操作响应"""
|
||||
success: bool
|
||||
message: str
|
||||
service_id: str
|
||||
status: str
|
||||
|
||||
|
||||
class ServiceStatusResponse(BaseModel):
|
||||
"""服务状态响应"""
|
||||
success: bool
|
||||
status: str
|
||||
health: str
|
||||
|
||||
|
||||
class ServiceLogsResponse(BaseModel):
|
||||
"""服务日志响应"""
|
||||
success: bool
|
||||
logs: List[str]
|
||||
|
||||
|
||||
class RepositoryAlgorithmsResponse(BaseModel):
|
||||
"""仓库算法列表响应"""
|
||||
success: bool
|
||||
algorithms: List[Dict[str, Any]]
|
||||
|
||||
|
||||
# 初始化服务组件
|
||||
project_analyzer = ProjectAnalyzer()
|
||||
service_generator = ServiceGenerator()
|
||||
service_orchestrator = ServiceOrchestrator()
|
||||
|
||||
|
||||
@router.post("/register", status_code=status.HTTP_201_CREATED)
|
||||
async def register_service(
|
||||
request: RegisterServiceRequest,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""注册新服务"""
|
||||
# 检查用户权限
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 1. 获取仓库信息
|
||||
# 注意:在实际实现中,应该从数据库中获取仓库信息
|
||||
# 这里简化处理,假设仓库存在
|
||||
|
||||
# 2. 分析项目
|
||||
repo_path = f"/tmp/repository_{request.repository_id}"
|
||||
# 注意:在实际实现中,应该从算法仓库中获取项目文件
|
||||
# 这里简化处理,创建一个模拟的项目结构
|
||||
os.makedirs(repo_path, exist_ok=True)
|
||||
|
||||
# 创建模拟的算法文件
|
||||
with open(os.path.join(repo_path, "algorithm.py"), "w") as f:
|
||||
f.write("""
|
||||
def predict(data):
|
||||
return {"result": "Prediction result", "input": data}
|
||||
|
||||
def run(data):
|
||||
return {"result": "Run result", "input": data}
|
||||
|
||||
def main(data):
|
||||
return {"result": "Main result", "input": data}
|
||||
""")
|
||||
|
||||
# 分析项目
|
||||
project_info = project_analyzer.analyze_project(repo_path)
|
||||
if not project_info["success"]:
|
||||
raise HTTPException(status_code=400, detail=f"项目分析失败: {project_info['error']}")
|
||||
|
||||
# 3. 生成服务包装器
|
||||
service_config = {
|
||||
"name": request.name,
|
||||
"version": request.version,
|
||||
"service_type": request.service_type,
|
||||
"host": request.host,
|
||||
"port": request.port,
|
||||
"timeout": request.timeout,
|
||||
"health_check_path": request.health_check_path,
|
||||
"environment": request.environment
|
||||
}
|
||||
|
||||
generate_result = service_generator.generate_service(project_info, service_config)
|
||||
if not generate_result["success"]:
|
||||
raise HTTPException(status_code=400, detail=f"服务生成失败: {generate_result['error']}")
|
||||
|
||||
# 4. 部署服务
|
||||
service_id = str(uuid.uuid4())
|
||||
deploy_result = service_orchestrator.deploy_service(service_id, service_config, project_info)
|
||||
if not deploy_result["success"]:
|
||||
raise HTTPException(status_code=400, detail=f"服务部署失败: {deploy_result['error']}")
|
||||
|
||||
# 5. 保存服务信息到数据库
|
||||
new_service = AlgorithmService(
|
||||
id=str(uuid.uuid4()),
|
||||
service_id=service_id,
|
||||
name=request.name,
|
||||
algorithm_name="algorithm", # 注意:在实际实现中,应该从仓库信息中获取
|
||||
version=request.version,
|
||||
host=request.host,
|
||||
port=request.port,
|
||||
api_url=deploy_result["api_url"],
|
||||
status=deploy_result["status"],
|
||||
config={
|
||||
"service_type": request.service_type,
|
||||
"timeout": request.timeout,
|
||||
"health_check_path": request.health_check_path,
|
||||
"environment": request.environment,
|
||||
"container_id": deploy_result["container_id"]
|
||||
}
|
||||
)
|
||||
|
||||
db.add(new_service)
|
||||
db.commit()
|
||||
db.refresh(new_service)
|
||||
|
||||
# 6. 返回响应
|
||||
return {
|
||||
"success": True,
|
||||
"message": "服务注册成功",
|
||||
"service": {
|
||||
"id": new_service.id,
|
||||
"service_id": new_service.service_id,
|
||||
"name": new_service.name,
|
||||
"algorithm_name": new_service.algorithm_name,
|
||||
"version": new_service.version,
|
||||
"host": new_service.host,
|
||||
"port": new_service.port,
|
||||
"api_url": new_service.api_url,
|
||||
"status": new_service.status,
|
||||
"created_at": new_service.created_at.isoformat(),
|
||||
"updated_at": new_service.updated_at.isoformat()
|
||||
}
|
||||
}
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.get("", response_model=ServiceListResponse)
|
||||
async def list_services(
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取服务列表"""
|
||||
# 检查用户权限
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 查询服务列表
|
||||
services = db.query(AlgorithmService).all()
|
||||
|
||||
# 转换为响应格式
|
||||
service_list = []
|
||||
for service in services:
|
||||
service_list.append(ServiceResponse(
|
||||
id=service.id,
|
||||
service_id=service.service_id,
|
||||
name=service.name,
|
||||
algorithm_name=service.algorithm_name,
|
||||
version=service.version,
|
||||
host=service.host,
|
||||
port=service.port,
|
||||
api_url=service.api_url,
|
||||
status=service.status,
|
||||
created_at=service.created_at.isoformat(),
|
||||
updated_at=service.updated_at.isoformat()
|
||||
))
|
||||
|
||||
return ServiceListResponse(
|
||||
success=True,
|
||||
services=service_list
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.get("/{service_id}", response_model=ServiceDetailResponse)
|
||||
async def get_service(
|
||||
service_id: str,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取服务详情"""
|
||||
# 检查用户权限
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 查询服务
|
||||
service = db.query(AlgorithmService).filter(AlgorithmService.service_id == service_id).first()
|
||||
|
||||
if not service:
|
||||
raise HTTPException(status_code=404, detail="Service not found")
|
||||
|
||||
# 返回响应
|
||||
return ServiceDetailResponse(
|
||||
success=True,
|
||||
service=ServiceResponse(
|
||||
id=service.id,
|
||||
service_id=service.service_id,
|
||||
name=service.name,
|
||||
algorithm_name=service.algorithm_name,
|
||||
version=service.version,
|
||||
host=service.host,
|
||||
port=service.port,
|
||||
api_url=service.api_url,
|
||||
status=service.status,
|
||||
created_at=service.created_at.isoformat(),
|
||||
updated_at=service.updated_at.isoformat()
|
||||
)
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post("/{service_id}/start")
|
||||
async def start_service(
|
||||
service_id: str,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""启动服务"""
|
||||
# 检查用户权限
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 查询服务
|
||||
service = db.query(AlgorithmService).filter(AlgorithmService.service_id == service_id).first()
|
||||
|
||||
if not service:
|
||||
raise HTTPException(status_code=404, detail="Service not found")
|
||||
|
||||
# 获取容器ID
|
||||
container_id = service.config.get("container_id")
|
||||
if not container_id:
|
||||
raise HTTPException(status_code=400, detail="Container ID not found")
|
||||
|
||||
# 启动服务
|
||||
start_result = service_orchestrator.start_service(service_id, container_id)
|
||||
if not start_result["success"]:
|
||||
raise HTTPException(status_code=400, detail=f"服务启动失败: {start_result['error']}")
|
||||
|
||||
# 更新服务状态
|
||||
service.status = start_result["status"]
|
||||
db.commit()
|
||||
|
||||
# 返回响应
|
||||
return ServiceOperationResponse(
|
||||
success=True,
|
||||
message="服务启动成功",
|
||||
service_id=service_id,
|
||||
status=start_result["status"]
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post("/{service_id}/stop")
|
||||
async def stop_service(
|
||||
service_id: str,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""停止服务"""
|
||||
# 检查用户权限
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 查询服务
|
||||
service = db.query(AlgorithmService).filter(AlgorithmService.service_id == service_id).first()
|
||||
|
||||
if not service:
|
||||
raise HTTPException(status_code=404, detail="Service not found")
|
||||
|
||||
# 获取容器ID
|
||||
container_id = service.config.get("container_id")
|
||||
if not container_id:
|
||||
raise HTTPException(status_code=400, detail="Container ID not found")
|
||||
|
||||
# 停止服务
|
||||
stop_result = service_orchestrator.stop_service(service_id, container_id)
|
||||
if not stop_result["success"]:
|
||||
raise HTTPException(status_code=400, detail=f"服务停止失败: {stop_result['error']}")
|
||||
|
||||
# 更新服务状态
|
||||
service.status = stop_result["status"]
|
||||
db.commit()
|
||||
|
||||
# 返回响应
|
||||
return ServiceOperationResponse(
|
||||
success=True,
|
||||
message="服务停止成功",
|
||||
service_id=service_id,
|
||||
status=stop_result["status"]
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post("/{service_id}/restart")
|
||||
async def restart_service(
|
||||
service_id: str,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""重启服务"""
|
||||
# 检查用户权限
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 查询服务
|
||||
service = db.query(AlgorithmService).filter(AlgorithmService.service_id == service_id).first()
|
||||
|
||||
if not service:
|
||||
raise HTTPException(status_code=404, detail="Service not found")
|
||||
|
||||
# 获取容器ID
|
||||
container_id = service.config.get("container_id")
|
||||
if not container_id:
|
||||
raise HTTPException(status_code=400, detail="Container ID not found")
|
||||
|
||||
# 重启服务
|
||||
restart_result = service_orchestrator.restart_service(service_id, container_id)
|
||||
if not restart_result["success"]:
|
||||
raise HTTPException(status_code=400, detail=f"服务重启失败: {restart_result['error']}")
|
||||
|
||||
# 更新服务状态
|
||||
service.status = restart_result["status"]
|
||||
db.commit()
|
||||
|
||||
# 返回响应
|
||||
return ServiceOperationResponse(
|
||||
success=True,
|
||||
message="服务重启成功",
|
||||
service_id=service_id,
|
||||
status=restart_result["status"]
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.delete("/{service_id}")
|
||||
async def delete_service(
|
||||
service_id: str,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""删除服务"""
|
||||
# 检查用户权限
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 查询服务
|
||||
service = db.query(AlgorithmService).filter(AlgorithmService.service_id == service_id).first()
|
||||
|
||||
if not service:
|
||||
raise HTTPException(status_code=404, detail="Service not found")
|
||||
|
||||
# 获取容器ID和镜像名称
|
||||
container_id = service.config.get("container_id")
|
||||
image_name = f"algorithm-service-{service_id}:{service.version}"
|
||||
|
||||
# 删除服务
|
||||
delete_result = service_orchestrator.delete_service(service_id, container_id, image_name)
|
||||
if not delete_result["success"]:
|
||||
# 继续执行,即使Docker操作失败
|
||||
pass
|
||||
|
||||
# 删除数据库记录
|
||||
db.delete(service)
|
||||
db.commit()
|
||||
|
||||
# 返回响应
|
||||
return {
|
||||
"success": True,
|
||||
"message": "服务删除成功",
|
||||
"service_id": service_id
|
||||
}
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.get("/{service_id}/status")
|
||||
async def get_service_status(
|
||||
service_id: str,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取服务状态"""
|
||||
# 检查用户权限
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 查询服务
|
||||
service = db.query(AlgorithmService).filter(AlgorithmService.service_id == service_id).first()
|
||||
|
||||
if not service:
|
||||
raise HTTPException(status_code=404, detail="Service not found")
|
||||
|
||||
# 获取容器ID
|
||||
container_id = service.config.get("container_id")
|
||||
if not container_id:
|
||||
raise HTTPException(status_code=400, detail="Container ID not found")
|
||||
|
||||
# 获取服务状态
|
||||
status_result = service_orchestrator.get_service_status(container_id)
|
||||
if not status_result["success"]:
|
||||
raise HTTPException(status_code=400, detail=f"获取服务状态失败: {status_result['error']}")
|
||||
|
||||
# 返回响应
|
||||
return ServiceStatusResponse(
|
||||
success=True,
|
||||
status=status_result["status"],
|
||||
health=status_result["health"]
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.get("/{service_id}/logs")
|
||||
async def get_service_logs(
|
||||
service_id: str,
|
||||
lines: int = 100,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取服务日志"""
|
||||
# 检查用户权限
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 查询服务
|
||||
service = db.query(AlgorithmService).filter(AlgorithmService.service_id == service_id).first()
|
||||
|
||||
if not service:
|
||||
raise HTTPException(status_code=404, detail="Service not found")
|
||||
|
||||
# 获取容器ID
|
||||
container_id = service.config.get("container_id")
|
||||
if not container_id:
|
||||
raise HTTPException(status_code=400, detail="Container ID not found")
|
||||
|
||||
# 获取服务日志
|
||||
logs_result = service_orchestrator.get_service_logs(container_id, lines)
|
||||
if not logs_result["success"]:
|
||||
raise HTTPException(status_code=400, detail=f"获取服务日志失败: {logs_result['error']}")
|
||||
|
||||
# 返回响应
|
||||
return ServiceLogsResponse(
|
||||
success=True,
|
||||
logs=logs_result["logs"]
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.get("/repository/algorithms")
|
||||
async def get_repository_algorithms(
|
||||
repository_id: str,
|
||||
current_user: dict = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取仓库中的算法列表"""
|
||||
# 检查用户权限
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
try:
|
||||
# 模拟获取仓库中的算法列表
|
||||
# 注意:在实际实现中,应该从算法仓库中获取真实的算法列表
|
||||
algorithms = [
|
||||
{
|
||||
"id": "alg-001",
|
||||
"name": "图像分类算法",
|
||||
"description": "基于深度学习的图像分类算法",
|
||||
"type": "computer_vision",
|
||||
"entry_point": "algorithm.py"
|
||||
},
|
||||
{
|
||||
"id": "alg-002",
|
||||
"name": "文本分类算法",
|
||||
"description": "基于BERT的文本分类算法",
|
||||
"type": "nlp",
|
||||
"entry_point": "text_algorithm.py"
|
||||
}
|
||||
]
|
||||
|
||||
return RepositoryAlgorithmsResponse(
|
||||
success=True,
|
||||
algorithms=algorithms
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
129
backend/app/routes/user.py
Normal file
129
backend/app/routes/user.py
Normal file
@@ -0,0 +1,129 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
|
||||
from app.models.database import get_db
|
||||
from app.schemas.user import UserCreate, UserUpdate, UserResponse, UserListResponse, Token, LoginRequest
|
||||
from app.services.user import UserService
|
||||
|
||||
# 创建路由器
|
||||
router = APIRouter(prefix="/users", tags=["users"])
|
||||
|
||||
# OAuth2密码Bearer
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/users/login")
|
||||
|
||||
|
||||
async def get_current_active_user(db: Session = Depends(get_db), token: str = Depends(oauth2_scheme)):
|
||||
"""获取当前活跃用户"""
|
||||
user = UserService.get_current_user(db, token)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
if user.status != "active":
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
return user
|
||||
|
||||
|
||||
from app.schemas.user import LoginRequest
|
||||
|
||||
@router.post("/login", response_model=Token)
|
||||
async def login(login_request: LoginRequest, db: Session = Depends(get_db)):
|
||||
"""用户登录"""
|
||||
user = UserService.authenticate_user(db, login_request.username, login_request.password)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Incorrect username or password",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
# 创建访问令牌
|
||||
access_token = UserService.create_access_token(
|
||||
data={"sub": user.username, "user_id": user.id}
|
||||
)
|
||||
|
||||
return {"access_token": access_token, "token_type": "bearer"}
|
||||
|
||||
|
||||
@router.post("/register", response_model=UserResponse)
|
||||
async def register(user: UserCreate, db: Session = Depends(get_db)):
|
||||
"""用户注册"""
|
||||
# 检查用户名是否已存在
|
||||
if UserService.get_user_by_username(db, user.username):
|
||||
raise HTTPException(status_code=400, detail="Username already registered")
|
||||
|
||||
# 检查邮箱是否已存在
|
||||
if UserService.get_user_by_email(db, user.email):
|
||||
raise HTTPException(status_code=400, detail="Email already registered")
|
||||
|
||||
# 创建用户
|
||||
db_user = UserService.create_user(db, user)
|
||||
|
||||
return db_user
|
||||
|
||||
|
||||
@router.get("/me", response_model=UserResponse)
|
||||
async def read_users_me(current_user: UserResponse = Depends(get_current_active_user)):
|
||||
"""获取当前用户信息"""
|
||||
return current_user
|
||||
|
||||
|
||||
@router.get("/", response_model=UserListResponse)
|
||||
async def get_users(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
current_user: UserResponse = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取用户列表"""
|
||||
# 只有管理员可以查看用户列表
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
users = UserService.get_users(db, skip=skip, limit=limit)
|
||||
return {"users": users, "total": len(users)}
|
||||
|
||||
|
||||
@router.get("/{user_id}", response_model=UserResponse)
|
||||
async def get_user(
|
||||
user_id: str,
|
||||
current_user: UserResponse = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取用户信息"""
|
||||
# 只有管理员或用户本人可以查看用户信息
|
||||
if current_user.role != "admin" and current_user.id != user_id:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
user = UserService.get_user_by_id(db, user_id)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
return user
|
||||
|
||||
|
||||
@router.put("/{user_id}", response_model=UserResponse)
|
||||
async def update_user(
|
||||
user_id: str,
|
||||
user_update: UserUpdate,
|
||||
current_user: UserResponse = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""更新用户信息"""
|
||||
# 只有管理员或用户本人可以更新用户信息
|
||||
if current_user.role != "admin" and current_user.id != user_id:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
# 非管理员只能更新自己的信息,不能更新角色
|
||||
if current_user.role != "admin" and "role" in user_update.dict():
|
||||
raise HTTPException(status_code=403, detail="Cannot update role")
|
||||
|
||||
user = UserService.update_user(db, user_id, user_update)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
return user
|
||||
Reference in New Issue
Block a user