仓库模块完了

This commit is contained in:
2026-02-08 20:06:35 +08:00
parent 20e1deae21
commit f145df4fa6
29 changed files with 1415 additions and 993 deletions

View File

@@ -0,0 +1,125 @@
# 服务注册管理实施计划
## 项目现状分析
当前项目使用Vue 3 + FastAPI + PostgreSQL技术栈已经实现了基本的服务注册功能但存在以下问题
1. **前端认证机制不完善**API调用缺少认证导致加载仓库列表失败
2. **服务注册功能不完整**:使用模拟数据,缺少真实的仓库信息获取
3. **服务管理能力有限**:缺少服务分组、批量管理和监控功能
4. **数据库管理界面缺失**:无法直接查看数据库中的服务和仓库信息
## 实施计划
### 第一阶段:修复基础功能(已完成)
#### 1. 完善前端认证机制
- **修改前端API调用**确保所有API调用都使用axios并自动携带认证token
- **实现token管理**添加token过期检测和自动刷新机制
- **优化登录状态**:实现用户登录状态持久化和自动恢复
#### 2. 修复服务注册流程
- **实现真实仓库列表加载**调用后端API获取数据库中的仓库信息
- **完善服务注册表单**:移除算法选择,添加仓库描述和地址展示
- **优化表单验证**:添加更严格的表单验证和错误提示
#### 3. 增强后端服务注册API
- **实现真实仓库信息获取**:从数据库中查询仓库详细信息
- **完善服务注册逻辑**:实现真实的服务创建和部署
- **添加错误处理**增强API错误处理和异常捕获
### 第二阶段:核心功能实现
#### 1. 服务分组前端界面
- **分组管理功能**:创建、编辑、删除分组的弹窗界面
- **服务分类展示**:左侧分组树状结构,右侧对应分组的服务列表
- **服务与分组关联**:服务编辑时选择分组的下拉菜单
- **界面设计**:清晰的视觉层次和交互反馈
#### 2. 服务监控功能
- **健康检查机制**后端定时任务检查服务状态支持HTTP、TCP和自定义检查方式
- **实时状态监控**使用WebSocket实现前端实时数据更新减少轮询开销
- **监控指标**CPU使用率、内存使用、响应时间、请求次数等核心指标
- **前端展示**:实时状态卡片、异常告警弹窗、监控指标列表
- **异常处理**:服务状态异常时触发告警,支持邮件通知(可选)
### 第三阶段:系统集成和优化
#### 1. 系统集成测试
- **测试范围**:服务分组管理、服务监控功能、服务注册流程、用户认证等核心功能
- **测试方法**单元测试pytest、API测试requests、前端集成测试Vue Test Utils
- **测试重点**:功能实现验证,确保所有核心功能正常运行
- **测试目标**:确保系统稳定性和可靠性,不做复杂的性能测试
#### 2. 功能优化和完善
- **数据库管理界面**:实现服务和仓库数据展示
- **服务列表优化**:实现分页、筛选和详情展示
- **API文档自动生成**使用FastAPI内置文档功能
- **文档完善**API文档和使用说明
## 技术实现细节
### 前端技术实现
- **使用Pinia管理状态**:实现用户登录状态和服务数据管理
- **使用Element Plus组件**构建美观的服务管理界面包括Tree、Table、Dialog等组件
- **使用axios拦截器**实现API调用的统一认证处理
- **使用WebSocket**:实现服务状态实时更新,减少轮询开销
### 后端技术实现
- **使用FastAPI构建API**实现高性能的服务管理API
- **使用SQLAlchemy操作数据库**:实现服务和仓库的持久化
- **使用JWT进行认证**:实现安全的用户认证
- **使用Docker管理服务**:实现服务的容器化部署
- **使用apscheduler**:实现后端定时任务,用于服务健康检查
- **使用websockets**实现后端WebSocket服务用于实时数据推送
### 数据库设计
- **服务表**:存储服务基本信息、配置和状态
- **服务分组表**:存储服务分组信息,与服务表建立一对多关系
- **仓库表**:存储算法仓库信息,包括名称、描述和地址
- **监控数据表**:存储服务监控指标和健康检查结果
### 核心API设计
#### 服务分组API
- `GET /api/service-groups`:获取所有服务分组
- `POST /api/service-groups`:创建新的服务分组
- `GET /api/service-groups/{group_id}`:获取单个服务分组详情
- `PUT /api/service-groups/{group_id}`:更新服务分组信息
- `DELETE /api/service-groups/{group_id}`:删除服务分组
#### 服务监控API
- `GET /api/services/{service_id}/status`:获取单个服务状态
- `GET /api/services/status`:获取所有服务状态
- `GET /api/services/{service_id}/metrics`:获取服务监控指标
- `GET /api/services/metrics`:获取所有服务监控指标
#### WebSocket API
- `ws://{host}:{port}/ws/services`:实时服务状态更新
- `ws://{host}:{port}/ws/metrics`:实时监控指标更新
## 预期效果
1. **稳定的服务注册**:用户可以正常注册新服务,系统能正确处理认证和数据存储
2. **高效的服务管理**:支持服务分组管理和服务分类展示,提供清晰的服务组织方式
3. **实时的服务监控**通过WebSocket实现服务状态实时更新及时发现和处理异常
4. **完整的数据展示**:可以查看数据库中的所有服务和仓库信息,支持服务详情查看
5. **良好的用户体验**:界面简洁直观,操作流程顺畅,响应速度快
6. **可扩展的架构**:支持后续服务数量的增加和功能扩展
## 风险评估
1. **认证问题**需要确保所有API调用都正确处理认证避免401错误
2. **WebSocket连接**需要处理WebSocket连接的稳定性和断线重连
3. **数据库性能**:需要优化数据库查询,确保服务管理的响应速度
4. **服务部署**:需要确保服务部署的可靠性和稳定性
5. **系统集成**:需要确保前后端和数据库的无缝集成
## 成功指标
1. **服务注册成功率**100%的服务注册请求能够成功处理
2. **服务管理响应时间**服务列表加载时间小于2秒操作响应时间小于1秒
3. **监控数据更新**监控数据更新延迟小于0.5秒,实现准实时监控
4. **系统稳定性**连续运行7天无故障服务监控功能正常运行
5. **用户满意度**:操作流程顺畅,界面美观易用,功能完整
6. **功能完整性**:所有核心功能(服务分组、服务监控、系统集成)都能正常实现

File diff suppressed because one or more lines are too long

View File

@@ -4,7 +4,7 @@ from fastapi.middleware.trustedhost import TrustedHostMiddleware
from app.config.settings import settings from app.config.settings import settings
from app.models.database import engine, Base from app.models.database import engine, Base
from app.routes import user, api_key, algorithm, openai, gateway, services, data_management, monitoring, permissions, history, deployment, gitea, repositories from app.routes import user, algorithm, openai, gateway, services, data_management, monitoring, permissions, history, deployment, gitea, repositories
# 创建数据库表 # 创建数据库表
Base.metadata.create_all(bind=engine) Base.metadata.create_all(bind=engine)
@@ -37,7 +37,6 @@ app.add_middleware(
# 注册路由 # 注册路由
app.include_router(user.router, prefix=settings.API_V1_STR) app.include_router(user.router, prefix=settings.API_V1_STR)
app.include_router(api_key.router, prefix=settings.API_V1_STR)
app.include_router(algorithm.router, prefix=settings.API_V1_STR) app.include_router(algorithm.router, prefix=settings.API_V1_STR)
app.include_router(openai.router, prefix=settings.API_V1_STR) app.include_router(openai.router, prefix=settings.API_V1_STR)
app.include_router(gateway.router, prefix=settings.API_V1_STR) app.include_router(gateway.router, prefix=settings.API_V1_STR)

View File

@@ -46,6 +46,20 @@ class AlgorithmVersion(Base):
calls = relationship("AlgorithmCall", back_populates="version") calls = relationship("AlgorithmCall", back_populates="version")
class Role(Base):
"""角色模型"""
__tablename__ = "roles"
id = Column(String, primary_key=True, index=True)
name = Column(String, unique=True, nullable=False, index=True) # admin, user
description = Column(Text, default="")
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
# 关系
users = relationship("User", back_populates="role")
class User(Base): class User(Base):
"""用户模型""" """用户模型"""
__tablename__ = "users" __tablename__ = "users"
@@ -54,31 +68,17 @@ class User(Base):
username = Column(String, unique=True, nullable=False, index=True) username = Column(String, unique=True, nullable=False, index=True)
email = Column(String, unique=True, nullable=False, index=True) email = Column(String, unique=True, nullable=False, index=True)
password_hash = Column(String, nullable=False) password_hash = Column(String, nullable=False)
role = Column(String, default="user", index=True) # admin, user, customer role_id = Column(String, ForeignKey("roles.id"), nullable=False, index=True)
status = Column(String, default="active", index=True) status = Column(String, default="active", index=True)
created_at = Column(DateTime(timezone=True), server_default=func.now()) created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now())
# 关系 # 关系
api_keys = relationship("APIKey", back_populates="user", cascade="all, delete-orphan")
calls = relationship("AlgorithmCall", back_populates="user") calls = relationship("AlgorithmCall", back_populates="user")
role = relationship("Role", back_populates="users")
class APIKey(Base):
"""API密钥模型"""
__tablename__ = "api_keys"
id = Column(String, primary_key=True, index=True)
user_id = Column(String, ForeignKey("users.id"), nullable=False, index=True)
key = Column(String, unique=True, nullable=False, index=True)
name = Column(String, nullable=False)
expires_at = Column(DateTime(timezone=True), nullable=False)
status = Column(String, default="active", index=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
# 关系
user = relationship("User", back_populates="api_keys")
class AlgorithmCall(Base): class AlgorithmCall(Base):
@@ -138,12 +138,28 @@ class AlgorithmRepository(Base):
algorithm = relationship("Algorithm", back_populates="repository", uselist=False) algorithm = relationship("Algorithm", back_populates="repository", uselist=False)
class ServiceGroup(Base):
"""服务分组模型"""
__tablename__ = "service_groups"
id = Column(String, primary_key=True, index=True)
name = Column(String, nullable=False, unique=True, index=True) # 分组名称
description = Column(Text, default="") # 分组描述
status = Column(String, default="active", index=True) # 状态
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
# 关系
services = relationship("AlgorithmService", back_populates="group")
class AlgorithmService(Base): class AlgorithmService(Base):
"""算法服务模型""" """算法服务模型"""
__tablename__ = "algorithm_services" __tablename__ = "algorithm_services"
id = Column(String, primary_key=True, index=True) id = Column(String, primary_key=True, index=True)
service_id = Column(String, unique=True, nullable=False, index=True) # 服务ID service_id = Column(String, unique=True, nullable=False, index=True) # 服务ID
group_id = Column(String, ForeignKey("service_groups.id"), nullable=True, index=True) # 分组ID
name = Column(String, nullable=False, index=True) # 服务名称 name = Column(String, nullable=False, index=True) # 服务名称
algorithm_name = Column(String, nullable=False) # 算法名称 algorithm_name = Column(String, nullable=False) # 算法名称
version = Column(String, nullable=False) # 版本 version = Column(String, nullable=False) # 版本
@@ -157,6 +173,9 @@ class AlgorithmService(Base):
created_at = Column(DateTime(timezone=True), server_default=func.now()) created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now())
# 关系
group = relationship("ServiceGroup", back_populates="services")
# 添加Algorithm模型的repository关系 # 添加Algorithm模型的repository关系
Algorithm.repository = relationship("AlgorithmRepository", back_populates="algorithm", uselist=False) Algorithm.repository = relationship("AlgorithmRepository", back_populates="algorithm", uselist=False)

View File

@@ -1,12 +1,11 @@
from fastapi import APIRouter from fastapi import APIRouter
from app.routes import user, algorithm, api_key, history, gateway, monitoring, openai, deployment from app.routes import user, algorithm, history, gateway, monitoring, openai, deployment
api_router = APIRouter() api_router = APIRouter()
# 注册路由 # 注册路由
api_router.include_router(user.router, prefix="/users", tags=["users"]) api_router.include_router(user.router, prefix="/users", tags=["users"])
api_router.include_router(algorithm.router, prefix="/algorithms", tags=["algorithms"]) 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(history.router, prefix="/history", tags=["history"])
api_router.include_router(gateway.router, prefix="/gateway", tags=["gateway"]) api_router.include_router(gateway.router, prefix="/gateway", tags=["gateway"])
api_router.include_router(monitoring.router, prefix="/monitoring", tags=["monitoring"]) api_router.include_router(monitoring.router, prefix="/monitoring", tags=["monitoring"])

View File

@@ -1,88 +0,0 @@
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"}

View File

@@ -243,22 +243,3 @@ async def get_user_role_based_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
}

View File

@@ -8,6 +8,7 @@ import uuid
from app.models.models import AlgorithmRepository from app.models.models import AlgorithmRepository
from app.models.database import SessionLocal from app.models.database import SessionLocal
from app.routes.user import get_current_active_user from app.routes.user import get_current_active_user
from app.schemas.user import UserResponse
from app.gitea.service import gitea_service from app.gitea.service import gitea_service
router = APIRouter(prefix="/repositories", tags=["repositories"]) router = APIRouter(prefix="/repositories", tags=["repositories"])
@@ -38,11 +39,11 @@ class UpdateRepositoryRequest(BaseModel):
@router.post("", status_code=status.HTTP_201_CREATED) @router.post("", status_code=status.HTTP_201_CREATED)
async def create_repository( async def create_repository(
request: CreateRepositoryRequest, request: CreateRepositoryRequest,
current_user: dict = Depends(get_current_active_user) current_user: UserResponse = Depends(get_current_active_user)
): ):
"""创建算法仓库""" """创建算法仓库"""
# 检查用户权限 # 检查用户权限
if current_user.role != "admin": if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Insufficient permissions") raise HTTPException(status_code=403, detail="Insufficient permissions")
# 创建数据库会话 # 创建数据库会话
@@ -92,11 +93,11 @@ async def create_repository(
@router.get("") @router.get("")
async def list_repositories( async def list_repositories(
algorithm_id: Optional[str] = None, algorithm_id: Optional[str] = None,
current_user: dict = Depends(get_current_active_user) current_user: UserResponse = Depends(get_current_active_user)
): ):
"""获取算法仓库列表""" """获取算法仓库列表"""
# 检查用户权限 # 检查用户权限
if current_user.role != "admin": if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Insufficient permissions") raise HTTPException(status_code=403, detail="Insufficient permissions")
# 创建数据库会话 # 创建数据库会话
@@ -139,11 +140,11 @@ async def list_repositories(
@router.get("/{repo_id}") @router.get("/{repo_id}")
async def get_repository( async def get_repository(
repo_id: str, repo_id: str,
current_user: dict = Depends(get_current_active_user) current_user: UserResponse = Depends(get_current_active_user)
): ):
"""获取单个算法仓库""" """获取单个算法仓库"""
# 检查用户权限 # 检查用户权限
if current_user.role != "admin": if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Insufficient permissions") raise HTTPException(status_code=403, detail="Insufficient permissions")
# 创建数据库会话 # 创建数据库会话
@@ -179,11 +180,11 @@ async def get_repository(
async def update_repository( async def update_repository(
repo_id: str, repo_id: str,
request: UpdateRepositoryRequest, request: UpdateRepositoryRequest,
current_user: dict = Depends(get_current_active_user) current_user: UserResponse = Depends(get_current_active_user)
): ):
"""更新算法仓库""" """更新算法仓库"""
# 检查用户权限 # 检查用户权限
if current_user.role != "admin": if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Insufficient permissions") raise HTTPException(status_code=403, detail="Insufficient permissions")
# 创建数据库会话 # 创建数据库会话
@@ -239,11 +240,11 @@ async def update_repository(
@router.delete("/{repo_id}") @router.delete("/{repo_id}")
async def delete_repository( async def delete_repository(
repo_id: str, repo_id: str,
current_user: dict = Depends(get_current_active_user) current_user: UserResponse = Depends(get_current_active_user)
): ):
"""删除算法仓库""" """删除算法仓库"""
# 检查用户权限 # 检查用户权限
if current_user.role != "admin": if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Insufficient permissions") raise HTTPException(status_code=403, detail="Insufficient permissions")
# 创建数据库会话 # 创建数据库会话

View File

@@ -6,9 +6,10 @@ from pydantic import BaseModel
import uuid import uuid
import os import os
from app.models.models import AlgorithmService from app.models.models import AlgorithmService, ServiceGroup, AlgorithmRepository
from app.models.database import SessionLocal from app.models.database import SessionLocal
from app.routes.user import get_current_active_user from app.routes.user import get_current_active_user
from app.schemas.user import UserResponse
from app.services.project_analyzer import ProjectAnalyzer from app.services.project_analyzer import ProjectAnalyzer
from app.services.service_generator import ServiceGenerator from app.services.service_generator import ServiceGenerator
from app.services.service_orchestrator import ServiceOrchestrator from app.services.service_orchestrator import ServiceOrchestrator
@@ -83,6 +84,46 @@ class RepositoryAlgorithmsResponse(BaseModel):
algorithms: List[Dict[str, Any]] algorithms: List[Dict[str, Any]]
class ServiceGroupRequest(BaseModel):
"""服务分组请求"""
name: str
description: str = ""
class ServiceGroupResponse(BaseModel):
"""服务分组响应"""
id: str
name: str
description: str
status: str
created_at: str
updated_at: str
class ServiceGroupListResponse(BaseModel):
"""服务分组列表响应"""
success: bool
groups: List[ServiceGroupResponse]
class ServiceGroupDetailResponse(BaseModel):
"""服务分组详情响应"""
success: bool
group: ServiceGroupResponse
class BatchOperationRequest(BaseModel):
"""批量操作请求"""
service_ids: List[str]
class BatchOperationResponse(BaseModel):
"""批量操作响应"""
success: bool
message: str
results: List[Dict[str, Any]]
# 初始化服务组件 # 初始化服务组件
project_analyzer = ProjectAnalyzer() project_analyzer = ProjectAnalyzer()
service_generator = ServiceGenerator() service_generator = ServiceGenerator()
@@ -92,19 +133,23 @@ service_orchestrator = ServiceOrchestrator()
@router.post("/register", status_code=status.HTTP_201_CREATED) @router.post("/register", status_code=status.HTTP_201_CREATED)
async def register_service( async def register_service(
request: RegisterServiceRequest, request: RegisterServiceRequest,
current_user: dict = Depends(get_current_active_user) current_user: UserResponse = Depends(get_current_active_user)
): ):
"""注册新服务""" """注册新服务"""
# 检查用户权限 # 检查用户权限
if current_user.role != "admin": if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Insufficient permissions") raise HTTPException(status_code=403, detail="Insufficient permissions")
# 创建数据库会话 # 创建数据库会话
db = SessionLocal() db = SessionLocal()
try: try:
# 1. 获取仓库信息 # 1. 获取仓库信息
# 注意:在实际实现中,应该从数据库中获取仓库信息 repo = db.query(AlgorithmRepository).filter(AlgorithmRepository.id == request.repository_id).first()
# 这里简化处理,假设仓库存在 if not repo:
raise HTTPException(status_code=404, detail="仓库不存在")
# 记录仓库信息
print(f"仓库信息: {repo.name}, {repo.description}, {repo.repo_url}")
# 2. 分析项目 # 2. 分析项目
repo_path = f"/tmp/repository_{request.repository_id}" repo_path = f"/tmp/repository_{request.repository_id}"
@@ -157,7 +202,7 @@ def main(data):
id=str(uuid.uuid4()), id=str(uuid.uuid4()),
service_id=service_id, service_id=service_id,
name=request.name, name=request.name,
algorithm_name="algorithm", # 注意:在实际实现中,应该从仓库信息中获取 algorithm_name=repo.name, # 使用仓库名称作为算法名称
version=request.version, version=request.version,
host=request.host, host=request.host,
port=request.port, port=request.port,
@@ -200,11 +245,11 @@ def main(data):
@router.get("", response_model=ServiceListResponse) @router.get("", response_model=ServiceListResponse)
async def list_services( async def list_services(
current_user: dict = Depends(get_current_active_user) current_user: UserResponse = Depends(get_current_active_user)
): ):
"""获取服务列表""" """获取服务列表"""
# 检查用户权限 # 检查用户权限
if current_user.role != "admin": if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Insufficient permissions") raise HTTPException(status_code=403, detail="Insufficient permissions")
# 创建数据库会话 # 创建数据库会话
@@ -241,11 +286,11 @@ async def list_services(
@router.get("/{service_id}", response_model=ServiceDetailResponse) @router.get("/{service_id}", response_model=ServiceDetailResponse)
async def get_service( async def get_service(
service_id: str, service_id: str,
current_user: dict = Depends(get_current_active_user) current_user: UserResponse = Depends(get_current_active_user)
): ):
"""获取服务详情""" """获取服务详情"""
# 检查用户权限 # 检查用户权限
if current_user.role != "admin": if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Insufficient permissions") raise HTTPException(status_code=403, detail="Insufficient permissions")
# 创建数据库会话 # 创建数据库会话
@@ -281,11 +326,11 @@ async def get_service(
@router.post("/{service_id}/start") @router.post("/{service_id}/start")
async def start_service( async def start_service(
service_id: str, service_id: str,
current_user: dict = Depends(get_current_active_user) current_user: UserResponse = Depends(get_current_active_user)
): ):
"""启动服务""" """启动服务"""
# 检查用户权限 # 检查用户权限
if current_user.role != "admin": if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Insufficient permissions") raise HTTPException(status_code=403, detail="Insufficient permissions")
# 创建数据库会话 # 创建数据库会话
@@ -325,11 +370,11 @@ async def start_service(
@router.post("/{service_id}/stop") @router.post("/{service_id}/stop")
async def stop_service( async def stop_service(
service_id: str, service_id: str,
current_user: dict = Depends(get_current_active_user) current_user: UserResponse = Depends(get_current_active_user)
): ):
"""停止服务""" """停止服务"""
# 检查用户权限 # 检查用户权限
if current_user.role != "admin": if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Insufficient permissions") raise HTTPException(status_code=403, detail="Insufficient permissions")
# 创建数据库会话 # 创建数据库会话
@@ -369,11 +414,11 @@ async def stop_service(
@router.post("/{service_id}/restart") @router.post("/{service_id}/restart")
async def restart_service( async def restart_service(
service_id: str, service_id: str,
current_user: dict = Depends(get_current_active_user) current_user: UserResponse = Depends(get_current_active_user)
): ):
"""重启服务""" """重启服务"""
# 检查用户权限 # 检查用户权限
if current_user.role != "admin": if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Insufficient permissions") raise HTTPException(status_code=403, detail="Insufficient permissions")
# 创建数据库会话 # 创建数据库会话
@@ -413,11 +458,11 @@ async def restart_service(
@router.delete("/{service_id}") @router.delete("/{service_id}")
async def delete_service( async def delete_service(
service_id: str, service_id: str,
current_user: dict = Depends(get_current_active_user) current_user: UserResponse = Depends(get_current_active_user)
): ):
"""删除服务""" """删除服务"""
# 检查用户权限 # 检查用户权限
if current_user.role != "admin": if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Insufficient permissions") raise HTTPException(status_code=403, detail="Insufficient permissions")
# 创建数据库会话 # 创建数据库会话
@@ -456,11 +501,11 @@ async def delete_service(
@router.get("/{service_id}/status") @router.get("/{service_id}/status")
async def get_service_status( async def get_service_status(
service_id: str, service_id: str,
current_user: dict = Depends(get_current_active_user) current_user: UserResponse = Depends(get_current_active_user)
): ):
"""获取服务状态""" """获取服务状态"""
# 检查用户权限 # 检查用户权限
if current_user.role != "admin": if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Insufficient permissions") raise HTTPException(status_code=403, detail="Insufficient permissions")
# 创建数据库会话 # 创建数据库会话
@@ -496,11 +541,11 @@ async def get_service_status(
async def get_service_logs( async def get_service_logs(
service_id: str, service_id: str,
lines: int = 100, lines: int = 100,
current_user: dict = Depends(get_current_active_user) current_user: UserResponse = Depends(get_current_active_user)
): ):
"""获取服务日志""" """获取服务日志"""
# 检查用户权限 # 检查用户权限
if current_user.role != "admin": if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Insufficient permissions") raise HTTPException(status_code=403, detail="Insufficient permissions")
# 创建数据库会话 # 创建数据库会话
@@ -534,11 +579,11 @@ async def get_service_logs(
@router.get("/repository/algorithms") @router.get("/repository/algorithms")
async def get_repository_algorithms( async def get_repository_algorithms(
repository_id: str, repository_id: str,
current_user: dict = Depends(get_current_active_user) current_user: UserResponse = Depends(get_current_active_user)
): ):
"""获取仓库中的算法列表""" """获取仓库中的算法列表"""
# 检查用户权限 # 检查用户权限
if current_user.role != "admin": if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Insufficient permissions") raise HTTPException(status_code=403, detail="Insufficient permissions")
try: try:
@@ -567,3 +612,453 @@ async def get_repository_algorithms(
) )
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
# 服务分组管理API
@router.post("/groups", status_code=status.HTTP_201_CREATED)
async def create_service_group(
request: ServiceGroupRequest,
current_user: UserResponse = Depends(get_current_active_user)
):
"""创建服务分组"""
# 检查用户权限
if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Insufficient permissions")
# 创建数据库会话
db = SessionLocal()
try:
# 生成唯一ID
group_id = str(uuid.uuid4())
# 创建分组实例
group = ServiceGroup(
id=group_id,
name=request.name,
description=request.description
)
# 保存到数据库
db.add(group)
db.commit()
db.refresh(group)
return {
"success": True,
"message": "服务分组创建成功",
"group": {
"id": group.id,
"name": group.name,
"description": group.description,
"status": group.status,
"created_at": group.created_at.isoformat(),
"updated_at": group.updated_at.isoformat()
}
}
finally:
db.close()
@router.get("/groups", response_model=ServiceGroupListResponse)
async def list_service_groups(
current_user: UserResponse = Depends(get_current_active_user)
):
"""获取服务分组列表"""
# 检查用户权限
if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Insufficient permissions")
# 创建数据库会话
db = SessionLocal()
try:
# 查询分组列表
groups = db.query(ServiceGroup).all()
# 转换为响应格式
group_list = []
for group in groups:
group_list.append(ServiceGroupResponse(
id=group.id,
name=group.name,
description=group.description,
status=group.status,
created_at=group.created_at.isoformat(),
updated_at=group.updated_at.isoformat()
))
return ServiceGroupListResponse(
success=True,
groups=group_list
)
finally:
db.close()
@router.get("/groups/{group_id}", response_model=ServiceGroupDetailResponse)
async def get_service_group(
group_id: str,
current_user: UserResponse = Depends(get_current_active_user)
):
"""获取服务分组详情"""
# 检查用户权限
if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Insufficient permissions")
# 创建数据库会话
db = SessionLocal()
try:
# 查询分组
group = db.query(ServiceGroup).filter(ServiceGroup.id == group_id).first()
if not group:
raise HTTPException(status_code=404, detail="Service group not found")
return ServiceGroupDetailResponse(
success=True,
group=ServiceGroupResponse(
id=group.id,
name=group.name,
description=group.description,
status=group.status,
created_at=group.created_at.isoformat(),
updated_at=group.updated_at.isoformat()
)
)
finally:
db.close()
@router.put("/groups/{group_id}")
async def update_service_group(
group_id: str,
request: ServiceGroupRequest,
current_user: UserResponse = Depends(get_current_active_user)
):
"""更新服务分组"""
# 检查用户权限
if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Insufficient permissions")
# 创建数据库会话
db = SessionLocal()
try:
# 查询分组
group = db.query(ServiceGroup).filter(ServiceGroup.id == group_id).first()
if not group:
raise HTTPException(status_code=404, detail="Service group not found")
# 更新分组信息
group.name = request.name
group.description = request.description
# 保存到数据库
db.commit()
db.refresh(group)
return {
"success": True,
"message": "服务分组更新成功",
"group": {
"id": group.id,
"name": group.name,
"description": group.description,
"status": group.status,
"created_at": group.created_at.isoformat(),
"updated_at": group.updated_at.isoformat()
}
}
finally:
db.close()
@router.delete("/groups/{group_id}")
async def delete_service_group(
group_id: str,
current_user: UserResponse = Depends(get_current_active_user)
):
"""删除服务分组"""
# 检查用户权限
if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Insufficient permissions")
# 创建数据库会话
db = SessionLocal()
try:
# 查询分组
group = db.query(ServiceGroup).filter(ServiceGroup.id == group_id).first()
if not group:
raise HTTPException(status_code=404, detail="Service group not found")
# 检查分组是否有服务
services_count = db.query(AlgorithmService).filter(AlgorithmService.group_id == group_id).count()
if services_count > 0:
raise HTTPException(status_code=400, detail=f"该分组下还有{services_count}个服务,无法删除")
# 删除分组
db.delete(group)
db.commit()
return {
"success": True,
"message": "服务分组删除成功",
"group_id": group_id
}
finally:
db.close()
# 批量服务操作API
@router.post("/batch/start")
async def batch_start_services(
request: BatchOperationRequest,
current_user: UserResponse = Depends(get_current_active_user)
):
"""批量启动服务"""
# 检查用户权限
if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Insufficient permissions")
# 创建数据库会话
db = SessionLocal()
try:
results = []
success_count = 0
for service_id in request.service_ids:
# 查询服务
service = db.query(AlgorithmService).filter(AlgorithmService.service_id == service_id).first()
if not service:
results.append({
"service_id": service_id,
"success": False,
"message": "服务不存在"
})
continue
# 获取容器ID
container_id = service.config.get("container_id")
if not container_id:
results.append({
"service_id": service_id,
"success": False,
"message": "容器ID不存在"
})
continue
# 启动服务
start_result = service_orchestrator.start_service(service_id, container_id)
if start_result["success"]:
# 更新服务状态
service.status = start_result["status"]
db.commit()
success_count += 1
results.append({
"service_id": service_id,
"success": True,
"message": "服务启动成功"
})
else:
results.append({
"service_id": service_id,
"success": False,
"message": f"服务启动失败: {start_result['error']}"
})
return BatchOperationResponse(
success=True,
message=f"批量启动完成,成功{success_count}个,失败{len(request.service_ids) - success_count}",
results=results
)
finally:
db.close()
@router.post("/batch/stop")
async def batch_stop_services(
request: BatchOperationRequest,
current_user: UserResponse = Depends(get_current_active_user)
):
"""批量停止服务"""
# 检查用户权限
if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Insufficient permissions")
# 创建数据库会话
db = SessionLocal()
try:
results = []
success_count = 0
for service_id in request.service_ids:
# 查询服务
service = db.query(AlgorithmService).filter(AlgorithmService.service_id == service_id).first()
if not service:
results.append({
"service_id": service_id,
"success": False,
"message": "服务不存在"
})
continue
# 获取容器ID
container_id = service.config.get("container_id")
if not container_id:
results.append({
"service_id": service_id,
"success": False,
"message": "容器ID不存在"
})
continue
# 停止服务
stop_result = service_orchestrator.stop_service(service_id, container_id)
if stop_result["success"]:
# 更新服务状态
service.status = stop_result["status"]
db.commit()
success_count += 1
results.append({
"service_id": service_id,
"success": True,
"message": "服务停止成功"
})
else:
results.append({
"service_id": service_id,
"success": False,
"message": f"服务停止失败: {stop_result['error']}"
})
return BatchOperationResponse(
success=True,
message=f"批量停止完成,成功{success_count}个,失败{len(request.service_ids) - success_count}",
results=results
)
finally:
db.close()
@router.post("/batch/restart")
async def batch_restart_services(
request: BatchOperationRequest,
current_user: UserResponse = Depends(get_current_active_user)
):
"""批量重启服务"""
# 检查用户权限
if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Insufficient permissions")
# 创建数据库会话
db = SessionLocal()
try:
results = []
success_count = 0
for service_id in request.service_ids:
# 查询服务
service = db.query(AlgorithmService).filter(AlgorithmService.service_id == service_id).first()
if not service:
results.append({
"service_id": service_id,
"success": False,
"message": "服务不存在"
})
continue
# 获取容器ID
container_id = service.config.get("container_id")
if not container_id:
results.append({
"service_id": service_id,
"success": False,
"message": "容器ID不存在"
})
continue
# 重启服务
restart_result = service_orchestrator.restart_service(service_id, container_id)
if restart_result["success"]:
# 更新服务状态
service.status = restart_result["status"]
db.commit()
success_count += 1
results.append({
"service_id": service_id,
"success": True,
"message": "服务重启成功"
})
else:
results.append({
"service_id": service_id,
"success": False,
"message": f"服务重启失败: {restart_result['error']}"
})
return BatchOperationResponse(
success=True,
message=f"批量重启完成,成功{success_count}个,失败{len(request.service_ids) - success_count}",
results=results
)
finally:
db.close()
@router.post("/batch/delete")
async def batch_delete_services(
request: BatchOperationRequest,
current_user: UserResponse = Depends(get_current_active_user)
):
"""批量删除服务"""
# 检查用户权限
if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Insufficient permissions")
# 创建数据库会话
db = SessionLocal()
try:
results = []
success_count = 0
for service_id in request.service_ids:
# 查询服务
service = db.query(AlgorithmService).filter(AlgorithmService.service_id == service_id).first()
if not service:
results.append({
"service_id": service_id,
"success": False,
"message": "服务不存在"
})
continue
# 获取容器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)
# 删除数据库记录
db.delete(service)
db.commit()
success_count += 1
results.append({
"service_id": service_id,
"success": True,
"message": "服务删除成功"
})
return BatchOperationResponse(
success=True,
message=f"批量删除完成,成功{success_count}个,失败{len(request.service_ids) - success_count}",
results=results
)
finally:
db.close()

View File

@@ -2,9 +2,12 @@ from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import List from typing import List
from jose import JWTError, jwt
from app.config.settings import settings
from app.models.database import get_db from app.models.database import get_db
from app.schemas.user import UserCreate, UserUpdate, UserResponse, UserListResponse, Token, LoginRequest from app.models.models import User, Role
from app.schemas.user import UserCreate, UserUpdate, UserResponse, UserListResponse, Token, LoginRequest, RoleCreate, RoleResponse
from app.services.user import UserService from app.services.user import UserService
# 创建路由器 # 创建路由器
@@ -16,16 +19,72 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/users/login")
async def get_current_active_user(db: Session = Depends(get_db), token: str = Depends(oauth2_scheme)): async def get_current_active_user(db: Session = Depends(get_db), token: str = Depends(oauth2_scheme)):
"""获取当前活跃用户""" """获取当前活跃用户"""
user = UserService.get_current_user(db, token) try:
# 检查令牌是否在黑名单中
if UserService.is_token_blacklisted(token):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
# 解码令牌
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
# 使用UserService获取用户信息避免直接使用User模型
user = UserService.get_user_by_username(db, username)
if not user: if not user:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials", detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"}, headers={"WWW-Authenticate": "Bearer"},
) )
# 检查用户是否活跃
if user.status != "active": if user.status != "active":
raise HTTPException(status_code=400, detail="Inactive user") raise HTTPException(status_code=400, detail="Inactive user")
return user
# 使用UserService获取角色信息
role = UserService.get_role_by_id(db, user.role_id)
# 构建角色响应
role_response = None
if role:
role_response = RoleResponse(
id=role.id,
name=role.name,
description=role.description,
created_at=role.created_at,
updated_at=role.updated_at
)
# 构建用户响应
user_response = UserResponse(
id=user.id,
username=user.username,
email=user.email,
role_id=user.role_id,
status=user.status,
created_at=user.created_at,
updated_at=user.updated_at,
role=role_response,
role_name=role.name if role else None
)
return user_response
except JWTError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
from app.schemas.user import LoginRequest from app.schemas.user import LoginRequest
@@ -60,6 +119,10 @@ async def register(user: UserCreate, db: Session = Depends(get_db)):
if UserService.get_user_by_email(db, user.email): if UserService.get_user_by_email(db, user.email):
raise HTTPException(status_code=400, detail="Email already registered") raise HTTPException(status_code=400, detail="Email already registered")
# 检查角色是否存在
if not UserService.get_role_by_id(db, user.role_id):
raise HTTPException(status_code=400, detail="Role not found")
# 创建用户 # 创建用户
db_user = UserService.create_user(db, user) db_user = UserService.create_user(db, user)
@@ -81,7 +144,7 @@ async def get_users(
): ):
"""获取用户列表""" """获取用户列表"""
# 只有管理员可以查看用户列表 # 只有管理员可以查看用户列表
if current_user.role != "admin": if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Not enough permissions") raise HTTPException(status_code=403, detail="Not enough permissions")
users = UserService.get_users(db, skip=skip, limit=limit) users = UserService.get_users(db, skip=skip, limit=limit)
@@ -96,7 +159,7 @@ async def get_user(
): ):
"""获取用户信息""" """获取用户信息"""
# 只有管理员或用户本人可以查看用户信息 # 只有管理员或用户本人可以查看用户信息
if current_user.role != "admin" and current_user.id != user_id: if current_user.role_name != "admin" and current_user.id != user_id:
raise HTTPException(status_code=403, detail="Not enough permissions") raise HTTPException(status_code=403, detail="Not enough permissions")
user = UserService.get_user_by_id(db, user_id) user = UserService.get_user_by_id(db, user_id)
@@ -115,15 +178,98 @@ async def update_user(
): ):
"""更新用户信息""" """更新用户信息"""
# 只有管理员或用户本人可以更新用户信息 # 只有管理员或用户本人可以更新用户信息
if current_user.role != "admin" and current_user.id != user_id: if current_user.role_name != "admin" and current_user.id != user_id:
raise HTTPException(status_code=403, detail="Not enough permissions") raise HTTPException(status_code=403, detail="Not enough permissions")
# 非管理员只能更新自己的信息,不能更新角色 # 非管理员只能更新自己的信息,不能更新角色
if current_user.role != "admin" and "role" in user_update.dict(): if current_user.role_name != "admin" and "role_id" in user_update.dict():
raise HTTPException(status_code=403, detail="Cannot update role") raise HTTPException(status_code=403, detail="Cannot update role")
# 检查角色是否存在
if "role_id" in user_update.dict():
if not UserService.get_role_by_id(db, user_update.role_id):
raise HTTPException(status_code=400, detail="Role not found")
user = UserService.update_user(db, user_id, user_update) user = UserService.update_user(db, user_id, user_update)
if not user: if not user:
raise HTTPException(status_code=404, detail="User not found") raise HTTPException(status_code=404, detail="User not found")
return user return user
@router.delete("/{user_id}")
async def delete_user(
user_id: str,
current_user: UserResponse = Depends(get_current_active_user),
db: Session = Depends(get_db)
):
"""删除用户"""
# 只有管理员可以删除用户
if current_user.role_name != "admin":
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")
# 删除用户
db.delete(user)
db.commit()
return {"message": "User deleted successfully"}
# 角色管理API
@router.post("/roles", response_model=RoleResponse)
async def create_role(
role: RoleCreate,
current_user: UserResponse = Depends(get_current_active_user),
db: Session = Depends(get_db)
):
"""创建角色"""
# 只有管理员可以创建角色
if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Not enough permissions")
# 检查角色名称是否已存在
if UserService.get_role_by_name(db, role.name):
raise HTTPException(status_code=400, detail="Role name already exists")
# 创建角色
db_role = UserService.create_role(db, role)
return db_role
@router.get("/roles", response_model=List[RoleResponse])
async def get_roles(
current_user: UserResponse = Depends(get_current_active_user),
db: Session = Depends(get_db)
):
"""获取角色列表"""
# 只有管理员可以查看所有角色
if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Not enough permissions")
roles = UserService.get_roles(db)
return roles
@router.get("/roles/{role_id}", response_model=RoleResponse)
async def get_role(
role_id: str,
current_user: UserResponse = Depends(get_current_active_user),
db: Session = Depends(get_db)
):
"""获取角色详情"""
# 只有管理员可以查看角色详情
if current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Not enough permissions")
role = UserService.get_role_by_id(db, role_id)
if not role:
raise HTTPException(status_code=404, detail="Role not found")
return role

View File

@@ -3,12 +3,30 @@ from typing import Optional, List
from datetime import datetime from datetime import datetime
class RoleBase(BaseModel):
"""角色基础模式"""
id: Optional[str] = None
name: str = Field(..., description="角色名称")
description: str = Field(default="", description="角色描述")
class RoleCreate(RoleBase):
"""创建角色模式"""
pass
class RoleResponse(RoleBase):
"""角色响应模式"""
created_at: datetime
updated_at: Optional[datetime] = None
class UserBase(BaseModel): class UserBase(BaseModel):
"""用户基础模式""" """用户基础模式"""
id: Optional[str] = None id: Optional[str] = None
username: str = Field(..., description="用户名") username: str = Field(..., description="用户名")
email: EmailStr = Field(..., description="邮箱") email: EmailStr = Field(..., description="邮箱")
role: str = Field(default="user", description="用户角色") role_id: str = Field(..., description="角色ID")
class UserCreate(UserBase): class UserCreate(UserBase):
@@ -20,7 +38,7 @@ class UserUpdate(BaseModel):
"""更新用户模式""" """更新用户模式"""
username: Optional[str] = None username: Optional[str] = None
email: Optional[EmailStr] = None email: Optional[EmailStr] = None
role: Optional[str] = None role_id: Optional[str] = None
status: Optional[str] = None status: Optional[str] = None
password: Optional[str] = None password: Optional[str] = None
@@ -30,6 +48,8 @@ class UserResponse(UserBase):
status: str status: str
created_at: datetime created_at: datetime
updated_at: Optional[datetime] = None updated_at: Optional[datetime] = None
role: Optional[RoleResponse] = None
role_name: Optional[str] = None
class UserListResponse(BaseModel): class UserListResponse(BaseModel):
@@ -56,28 +76,4 @@ class LoginRequest(BaseModel):
password: str = Field(..., description="密码") password: str = Field(..., description="密码")
class APIKeyBase(BaseModel):
"""API密钥基础模式"""
id: Optional[str] = None
user_id: str = Field(..., description="用户ID")
name: str = Field(..., description="密钥名称")
class APIKeyCreate(APIKeyBase):
"""创建API密钥模式"""
expires_at: datetime = Field(..., description="过期时间")
class APIKeyResponse(APIKeyBase):
"""API密钥响应模式"""
key: str
expires_at: datetime
status: str
created_at: datetime
updated_at: Optional[datetime] = None
class APIKeyListResponse(BaseModel):
"""API密钥列表响应模式"""
api_keys: List[APIKeyResponse]
total: int

View File

@@ -8,7 +8,7 @@ import logging
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy import and_, or_ from sqlalchemy import and_, or_
from app.models.models import User, Algorithm, APIKey from app.models.models import User, Algorithm
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -209,26 +209,7 @@ class PermissionManager:
logger.error(f"Error getting algorithm permissions: {str(e)}") logger.error(f"Error getting algorithm permissions: {str(e)}")
return [] return []
def check_api_key_access(self, db: Session, api_key_value: str, algorithm_id: str) -> bool:
"""检查API密钥对算法的访问权限"""
try:
# 通过API密钥查找用户
api_key = db.query(APIKey).filter(
APIKey.key == api_key_value,
APIKey.status == "active"
).first()
if not api_key:
return False
# 检查用户对算法的访问权限
return self.check_algorithm_access(
db, api_key.user_id, algorithm_id, PermissionType.EXECUTE
)
except Exception as e:
logger.error(f"Error checking API key access: {str(e)}")
return False
def validate_user_algorithm_operation(self, db: Session, user_id: str, algorithm_id: str, def validate_user_algorithm_operation(self, db: Session, user_id: str, algorithm_id: str,
operation: str) -> bool: operation: str) -> bool:

View File

@@ -2,12 +2,12 @@ from datetime import datetime, timedelta
from typing import Optional, List from typing import Optional, List
from jose import JWTError, jwt from jose import JWTError, jwt
from passlib.context import CryptContext from passlib.context import CryptContext
from sqlalchemy.orm import Session from sqlalchemy.orm import Session, joinedload
import uuid import uuid
from app.config.settings import settings from app.config.settings import settings
from app.models.models import User, APIKey from app.models.models import User, Role
from app.schemas.user import UserCreate, UserUpdate, TokenData, APIKeyCreate from app.schemas.user import UserCreate, UserUpdate, TokenData, RoleCreate
from app.utils.cache import cache from app.utils.cache import cache
# 密码加密上下文使用pbkdf2_sha256方案避免bcrypt的密码长度限制 # 密码加密上下文使用pbkdf2_sha256方案避免bcrypt的密码长度限制
@@ -86,12 +86,12 @@ class UserService:
@staticmethod @staticmethod
def get_user_by_username(db: Session, username: str) -> Optional[User]: def get_user_by_username(db: Session, username: str) -> Optional[User]:
"""通过用户名获取用户""" """通过用户名获取用户"""
return db.query(User).filter(User.username == username).first() return db.query(User).options(joinedload(User.role)).filter(User.username == username).first()
@staticmethod @staticmethod
def get_user_by_id(db: Session, user_id: str) -> Optional[User]: def get_user_by_id(db: Session, user_id: str) -> Optional[User]:
"""通过ID获取用户""" """通过ID获取用户"""
return db.query(User).filter(User.id == user_id).first() return db.query(User).options(joinedload(User.role)).filter(User.id == user_id).first()
@staticmethod @staticmethod
def get_user_by_email(db: Session, email: str) -> Optional[User]: def get_user_by_email(db: Session, email: str) -> Optional[User]:
@@ -101,7 +101,7 @@ class UserService:
@staticmethod @staticmethod
def get_users(db: Session, skip: int = 0, limit: int = 100) -> List[User]: def get_users(db: Session, skip: int = 0, limit: int = 100) -> List[User]:
"""获取用户列表""" """获取用户列表"""
return db.query(User).offset(skip).limit(limit).all() return db.query(User).options(joinedload(User.role)).offset(skip).limit(limit).all()
@staticmethod @staticmethod
def create_user(db: Session, user: UserCreate) -> User: def create_user(db: Session, user: UserCreate) -> User:
@@ -115,7 +115,7 @@ class UserService:
username=user.username, username=user.username,
email=user.email, email=user.email,
password_hash=UserService.get_password_hash(user.password), password_hash=UserService.get_password_hash(user.password),
role=user.role role_id=user.role_id
) )
# 保存到数据库 # 保存到数据库
@@ -160,79 +160,63 @@ class UserService:
return None return None
return user return user
class APIKeyService:
"""API密钥服务类"""
@staticmethod @staticmethod
def create_api_key(db: Session, api_key_create: APIKeyCreate) -> APIKey: def create_role(db: Session, role: RoleCreate) -> Role:
"""创建API密钥""" """创建角色"""
# 生成唯一ID和密钥 # 生成唯一ID
api_key_id = f"key-{uuid.uuid4().hex[:8]}" role_id = f"role-{uuid.uuid4().hex[:8]}"
api_key_value = f"sk_{uuid.uuid4().hex}"
# 创建API密钥实例 # 创建角色实例
db_api_key = APIKey( db_role = Role(
id=api_key_id, id=role_id,
user_id=api_key_create.user_id, name=role.name,
name=api_key_create.name, description=role.description
key=api_key_value,
expires_at=api_key_create.expires_at
) )
# 保存到数据库 # 保存到数据库
db.add(db_api_key) db.add(db_role)
db.commit() db.commit()
db.refresh(db_api_key) db.refresh(db_role)
return db_api_key return db_role
@staticmethod @staticmethod
def get_api_key_by_id(db: Session, api_key_id: str) -> Optional[APIKey]: def get_role_by_id(db: Session, role_id: str) -> Optional[Role]:
"""通过ID获取API密钥""" """通过ID获取角色"""
return db.query(APIKey).filter(APIKey.id == api_key_id).first() return db.query(Role).filter(Role.id == role_id).first()
@staticmethod @staticmethod
def get_api_key_by_value(db: Session, api_key_value: str) -> Optional[APIKey]: def get_role_by_name(db: Session, role_name: str) -> Optional[Role]:
"""通过值获取API密钥""" """通过名称获取角色"""
return db.query(APIKey).filter(APIKey.key == api_key_value).first() return db.query(Role).filter(Role.name == role_name).first()
@staticmethod @staticmethod
def get_api_keys_by_user_id(db: Session, user_id: str) -> List[APIKey]: def get_roles(db: Session, skip: int = 0, limit: int = 100) -> List[Role]:
"""通过用户ID获取API密钥列表""" """获取角色列表"""
return db.query(APIKey).filter(APIKey.user_id == user_id).all() return db.query(Role).offset(skip).limit(limit).all()
@staticmethod @staticmethod
def revoke_api_key(db: Session, api_key_id: str) -> Optional[APIKey]: def init_default_roles(db: Session) -> None:
"""撤销API密钥""" """初始化默认角色"""
# 获取API密钥 # 检查是否已存在默认角色
db_api_key = APIKeyService.get_api_key_by_id(db, api_key_id) admin_role = UserService.get_role_by_name(db, "admin")
if not db_api_key: user_role = UserService.get_role_by_name(db, "user")
return None
# 更新状态为已撤销 # 创建管理员角色
db_api_key.status = "revoked" if not admin_role:
admin_role = RoleCreate(
name="admin",
description="系统管理员,拥有所有权限"
)
UserService.create_role(db, admin_role)
# 保存到数据库 # 创建普通用户角色
db.commit() if not user_role:
db.refresh(db_api_key) user_role = RoleCreate(
name="user",
description="普通用户,拥有基本权限"
)
UserService.create_role(db, user_role)
return db_api_key
@staticmethod
def validate_api_key(db: Session, api_key_value: str) -> Optional[APIKey]:
"""验证API密钥"""
# 获取API密钥
api_key = APIKeyService.get_api_key_by_value(db, api_key_value)
if not api_key:
return None
# 检查状态
if api_key.status != "active":
return None
# 检查是否过期
if api_key.expires_at < datetime.utcnow():
return None
return api_key

79
backend/check_admin.py Normal file
View File

@@ -0,0 +1,79 @@
#!/usr/bin/env python3
"""
检查并重置管理员账号
"""
from sqlalchemy.orm import Session
from app.models.database import engine, Base, SessionLocal
from app.models.models import User, Role
from app.services.user import UserService
def check_admin():
"""检查并重置管理员账号"""
db = SessionLocal()
try:
# 检查是否存在管理员账号
print("检查管理员账号...")
admin_user = db.query(User).filter(User.username == "admin").first()
if not admin_user:
print("⚠️ 管理员账号不存在,创建新的管理员账号...")
# 初始化默认角色
UserService.init_default_roles(db)
# 获取默认管理员角色
admin_role = UserService.get_role_by_name(db, "admin")
if not admin_role:
print("❌ 管理员角色不存在,创建失败")
return
# 创建默认管理员账号
admin_user = User(
id="user-admin",
username="admin",
email="admin@example.com",
password_hash=UserService.get_password_hash("admin123"),
role_id=admin_role.id,
status="active"
)
db.add(admin_user)
db.commit()
db.refresh(admin_user)
print("✅ 管理员账号创建成功")
else:
print("✅ 管理员账号存在,重置密码...")
# 重置管理员密码
admin_user.password_hash = UserService.get_password_hash("admin123")
db.commit()
print("✅ 管理员密码重置成功")
# 显示管理员账号信息
print("\n管理员账号信息:")
print(f"用户名: {admin_user.username}")
print(f"密码: admin123")
print(f"邮箱: {admin_user.email}")
print(f"状态: {admin_user.status}")
# 检查角色信息
if admin_user.role:
print(f"角色: {admin_user.role.name}")
else:
print("⚠️ 管理员角色信息缺失")
# 尝试修复角色关联
admin_role = UserService.get_role_by_name(db, "admin")
if admin_role:
admin_user.role_id = admin_role.id
db.commit()
print("✅ 管理员角色关联修复成功")
except Exception as e:
print(f"❌ 操作失败: {e}")
finally:
db.close()
if __name__ == "__main__":
check_admin()

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
初始化数据库,创建默认管理员账号 初始化数据库,创建默认管理员账号和角色
""" """
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -16,11 +16,25 @@ def init_db():
Base.metadata.create_all(bind=engine) Base.metadata.create_all(bind=engine)
print("数据库表创建完成") print("数据库表创建完成")
# 创建默认管理员账号 # 初始化默认角色
print("\n创建默认管理员账号...") print("\n初始化默认角色...")
db = SessionLocal() db = SessionLocal()
try: try:
# 初始化默认角色
UserService.init_default_roles(db)
print("✅ 默认角色初始化完成")
# 获取默认角色
admin_role = UserService.get_role_by_name(db, "admin")
user_role = UserService.get_role_by_name(db, "user")
if not admin_role or not user_role:
print("❌ 默认角色创建失败")
return
# 创建默认管理员账号
print("\n创建默认管理员账号...")
# 检查是否已存在管理员账号 # 检查是否已存在管理员账号
admin_user = db.query(User).filter(User.username == "admin").first() admin_user = db.query(User).filter(User.username == "admin").first()
@@ -31,7 +45,7 @@ def init_db():
username="admin", username="admin",
email="admin@example.com", email="admin@example.com",
password_hash=UserService.get_password_hash("admin123"), password_hash=UserService.get_password_hash("admin123"),
role="admin", role_id=admin_role.id,
status="active" status="active"
) )
db.add(admin_user) db.add(admin_user)

View File

@@ -1,4 +1,4 @@
#!/bin/bash #!/bin/bash
cd /Users/duguoyou/MLFlow/algorithm-showcase/backend cd /Users/duguoyou/MLFlow/algorithm-showcase/backend
source venv/bin/activate source venv/bin/activate
PYTHONPATH=. uvicorn app.main:app --host 0.0.0.0 --port 8002 PYTHONPATH=. uvicorn app.main:app --host 0.0.0.0 --port 8001

127
backend/update_db.py Normal file
View File

@@ -0,0 +1,127 @@
#!/usr/bin/env python3
"""
更新数据库结构删除api_keys表添加roles表修改users表
"""
from sqlalchemy.orm import Session
from sqlalchemy import text
from app.models.database import engine, Base, SessionLocal
from app.models.models import User, Role
from app.services.user import UserService
def update_db():
"""更新数据库结构"""
db = SessionLocal()
try:
# 1. 删除api_keys表
print("删除api_keys表...")
try:
db.execute(text("DROP TABLE IF EXISTS api_keys CASCADE"))
db.commit()
print("✅ api_keys表删除成功")
except Exception as e:
print(f"⚠️ 删除api_keys表时出错: {e}")
db.rollback()
# 2. 创建roles表
print("\n创建roles表...")
try:
# 直接执行SQL创建表避免依赖模型的顺序
db.execute(text("""
CREATE TABLE IF NOT EXISTS roles (
id VARCHAR PRIMARY KEY,
name VARCHAR UNIQUE NOT NULL,
description TEXT DEFAULT '',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
)
"""))
db.commit()
print("✅ roles表创建成功")
except Exception as e:
print(f"⚠️ 创建roles表时出错: {e}")
db.rollback()
# 3. 修改users表添加role_id字段删除role字段
print("\n修改users表...")
try:
# 检查是否存在role_id字段
result = db.execute(text("SELECT column_name FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'role_id'")).fetchone()
if not result:
# 添加role_id字段
db.execute(text("ALTER TABLE users ADD COLUMN role_id VARCHAR"))
print("✅ 添加role_id字段成功")
# 初始化默认角色
UserService.init_default_roles(db)
print("✅ 默认角色初始化成功")
# 获取默认角色
admin_role = UserService.get_role_by_name(db, "admin")
user_role = UserService.get_role_by_name(db, "user")
if admin_role and user_role:
# 更新现有用户的role_id字段
db.execute(text(f"UPDATE users SET role_id = CASE WHEN role = 'admin' THEN '{admin_role.id}' ELSE '{user_role.id}' END"))
print("✅ 更新用户role_id字段成功")
# 删除role字段
result = db.execute(text("SELECT column_name FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'role'")).fetchone()
if result:
db.execute(text("ALTER TABLE users DROP COLUMN role"))
print("✅ 删除role字段成功")
# 添加外键约束
db.execute(text("ALTER TABLE users ADD CONSTRAINT fk_users_role FOREIGN KEY (role_id) REFERENCES roles(id)"))
print("✅ 添加外键约束成功")
db.commit()
except Exception as e:
print(f"⚠️ 修改users表时出错: {e}")
db.rollback()
# 4. 检查并创建默认管理员账号
print("\n检查默认管理员账号...")
try:
# 检查是否已存在管理员账号
admin_user = db.query(User).filter(User.username == "admin").first()
if not admin_user:
# 获取默认管理员角色
admin_role = UserService.get_role_by_name(db, "admin")
if admin_role:
# 创建默认管理员账号
admin_user = User(
id="user-admin",
username="admin",
email="admin@example.com",
password_hash=UserService.get_password_hash("admin123"),
role_id=admin_role.id,
status="active"
)
db.add(admin_user)
db.commit()
db.refresh(admin_user)
print("✅ 默认管理员账号创建成功")
print(f"用户名: admin")
print(f"密码: admin123")
else:
print("❌ 无法创建管理员账号因为admin角色不存在")
else:
print("⚠️ 管理员账号已存在")
except Exception as e:
print(f"⚠️ 检查管理员账号时出错: {e}")
db.rollback()
print("\n✅ 数据库结构更新完成")
finally:
db.close()
if __name__ == "__main__":
update_db()

View File

@@ -4255,3 +4255,12 @@ INFO: 127.0.0.1:58386 - "GET /api/v1/repositories HTTP/1.1" 200 OK
INFO: 127.0.0.1:58388 - "GET /api/v1/gitea/config HTTP/1.1" 200 OK INFO: 127.0.0.1:58388 - "GET /api/v1/gitea/config HTTP/1.1" 200 OK
INFO: 127.0.0.1:58409 - "GET /api/v1/repositories HTTP/1.1" 200 OK INFO: 127.0.0.1:58409 - "GET /api/v1/repositories HTTP/1.1" 200 OK
INFO: 127.0.0.1:58411 - "GET /api/v1/gitea/config HTTP/1.1" 200 OK INFO: 127.0.0.1:58411 - "GET /api/v1/gitea/config HTTP/1.1" 200 OK
INFO: 127.0.0.1:58972 - "GET /api/v1/repositories HTTP/1.1" 200 OK
INFO: 127.0.0.1:58976 - "GET /api/v1/gitea/config HTTP/1.1" 200 OK
INFO: 127.0.0.1:59054 - "GET /api/v1/repositories HTTP/1.1" 200 OK
INFO: 127.0.0.1:59058 - "GET /api/v1/gitea/config HTTP/1.1" 200 OK
INFO: 127.0.0.1:59080 - "GET /api/v1/repositories HTTP/1.1" 200 OK
INFO: 127.0.0.1:59084 - "GET /api/v1/gitea/config HTTP/1.1" 200 OK
INFO: 127.0.0.1:59104 - "GET /api/v1/repositories HTTP/1.1" 200 OK
INFO: 127.0.0.1:59108 - "GET /api/v1/gitea/config HTTP/1.1" 200 OK
INFO: 127.0.0.1:59318 - "GET /api/v1/repositories HTTP/1.1" 401 Unauthorized

View File

@@ -77,14 +77,7 @@ const routes: Array<RouteRecordRaw> = [
title: '用户管理' title: '用户管理'
} }
}, },
{
path: 'api-keys',
name: 'AdminApiKeys',
component: () => import('../views/admin/AdminApiKeysView.vue'),
meta: {
title: 'API密钥管理'
}
},
{ {
path: 'api', path: 'api',
name: 'AdminApiManagement', name: 'AdminApiManagement',
@@ -157,7 +150,9 @@ router.beforeEach((to, _from, next) => {
if (to.meta.requiresAdmin) { if (to.meta.requiresAdmin) {
try { try {
const userObj = JSON.parse(user) const userObj = JSON.parse(user)
if (userObj.role !== 'admin') { // 检查用户是否为管理员,支持多种格式
const isAdmin = userObj.role?.name === 'admin' || userObj.role_name === 'admin'
if (!isAdmin) {
next({ name: 'Home' }) next({ name: 'Home' })
return return
} }

View File

@@ -1,12 +1,20 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import axios from 'axios' import axios from 'axios'
// 定义角色类型
interface Role {
id: string
name: string
description: string
}
// 定义用户类型 // 定义用户类型
interface User { interface User {
id: string id: string
username: string username: string
email: string email: string
role: string role_id: string
role?: Role
status: string status: string
} }
@@ -21,7 +29,7 @@ interface RegisterRequest {
username: string username: string
password: string password: string
email: string email: string
role: string role_id: string
} }
// 定义用户存储 // 定义用户存储
@@ -35,7 +43,7 @@ export const useUserStore = defineStore('user', {
getters: { getters: {
isLoggedIn: (state) => !!state.token, isLoggedIn: (state) => !!state.token,
isAdmin: (state) => state.user?.role === 'admin' isAdmin: (state) => state.user?.role?.name === 'admin'
}, },
actions: { actions: {

View File

@@ -38,12 +38,7 @@
</template> </template>
<span>用户管理</span> <span>用户管理</span>
</el-menu-item> </el-menu-item>
<el-menu-item index="/admin/api-keys">
<template #icon>
<el-icon><key /></el-icon>
</template>
<span>API密钥管理</span>
</el-menu-item>
</el-menu> </el-menu>
</aside> </aside>
@@ -62,7 +57,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { DataAnalysis, User, Key, Link, Cpu } from '@element-plus/icons-vue' import { DataAnalysis, User, Link, Cpu } from '@element-plus/icons-vue'
// 获取路由和路由器 // 获取路由和路由器
const route = useRoute() const route = useRoute()

View File

@@ -28,11 +28,12 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useUserStore } from '../stores/user' import { useUserStore } from '../stores/user'
import { UserFilled, Lock } from '@element-plus/icons-vue' import { UserFilled, Lock } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import axios from 'axios'
// 获取路由和存储 // 获取路由和存储
const router = useRouter() const router = useRouter()
@@ -47,6 +48,10 @@ const registerForm = ref({
confirmPassword: '' confirmPassword: ''
}) })
// 角色列表和默认角色ID
const roles = ref<any[]>([])
const defaultRoleId = ref('')
// 加载状态 // 加载状态
const loading = ref(false) const loading = ref(false)
@@ -79,6 +84,23 @@ const registerRules = ref({
] ]
}) })
// 获取角色列表
const fetchRoles = async () => {
try {
const response = await axios.get('/api/users/roles')
roles.value = response.data
// 找到user角色的ID
const userRole = roles.value.find(r => r.name === 'user')
if (userRole) {
defaultRoleId.value = userRole.id
}
} catch (error) {
console.error('获取角色列表失败:', error)
ElMessage.error('获取角色列表失败,请稍后重试')
}
}
// 处理注册 // 处理注册
const handleRegister = async () => { const handleRegister = async () => {
if (!registerFormRef.value) return if (!registerFormRef.value) return
@@ -89,12 +111,18 @@ const handleRegister = async () => {
loading.value = true loading.value = true
try { try {
// 确保有默认角色ID
if (!defaultRoleId.value) {
ElMessage.error('获取角色信息失败,请刷新页面重试')
return
}
// 调用注册方法 // 调用注册方法
const success = await userStore.register({ const success = await userStore.register({
username: registerForm.value.username, username: registerForm.value.username,
email: registerForm.value.email, email: registerForm.value.email,
password: registerForm.value.password, password: registerForm.value.password,
role: 'user' // 默认角色 role_id: defaultRoleId.value // 使用默认角色ID
}) })
if (success) { if (success) {
@@ -117,6 +145,11 @@ const handleRegister = async () => {
} }
}) })
} }
// 组件挂载时获取角色列表
onMounted(() => {
fetchRoles()
})
</script> </script>
<style scoped> <style scoped>

View File

@@ -1,174 +0,0 @@
<template>
<div class="admin-api-keys">
<h2>API密钥管理</h2>
<div class="api-keys-actions">
<el-button type="primary" @click="showCreateDialog">创建API密钥</el-button>
</div>
<el-table :data="apiKeys" style="width: 100%" class="api-keys-table">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="密钥名称" />
<el-table-column prop="key" label="API密钥" show-overflow-tooltip />
<el-table-column prop="user_id" label="用户ID" width="100" />
<el-table-column prop="is_active" label="状态" width="100">
<template #default="scope">
<el-tag :type="scope.row.is_active ? 'success' : 'danger'">{{ scope.row.is_active ? '活跃' : '已停用' }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="expires_at" label="过期时间" width="180">
<template #default="scope">
{{ formatDate(scope.row.expires_at) || '永不过期' }}
</template>
</el-table-column>
<el-table-column prop="created_at" label="创建时间" width="180">
<template #default="scope">
{{ formatDate(scope.row.created_at) }}
</template>
</el-table-column>
<el-table-column label="操作" width="200">
<template #default="scope">
<el-button size="small" @click="toggleApiKey(scope.row.id, !scope.row.is_active)">{{ scope.row.is_active ? '停用' : '激活' }}</el-button>
<el-button size="small" type="danger" @click="deleteApiKey(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 创建API密钥对话框 -->
<el-dialog v-model="createDialogVisible" title="创建API密钥">
<el-form :model="createForm" label-width="100px">
<el-form-item label="密钥名称" required>
<el-input v-model="createForm.name" placeholder="请输入密钥名称" />
</el-form-item>
<el-form-item label="用户ID" required>
<el-input v-model="createForm.user_id" type="number" placeholder="请输入用户ID" />
</el-form-item>
<el-form-item label="过期时间">
<el-date-picker
v-model="createForm.expires_at"
type="datetime"
placeholder="选择过期时间"
style="width: 100%"
/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="createDialogVisible = false">取消</el-button>
<el-button type="primary" @click="createApiKey">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const apiKeys = ref<any[]>([
{
id: 1,
user_id: 1,
key: 'sk-1234567890abcdef1234567890abcdef',
name: '管理员密钥',
expires_at: null,
is_active: true,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
}
])
const createDialogVisible = ref(false)
const createForm = ref({
name: '',
user_id: 1,
expires_at: null
})
const formatDate = (dateString?: string | null) => {
if (!dateString) return ''
const date = new Date(dateString)
return date.toLocaleString()
}
const showCreateDialog = () => {
createForm.value = {
name: '',
user_id: 1,
expires_at: null
}
createDialogVisible.value = true
}
const createApiKey = async () => {
// 这里应该调用后端API创建API密钥
// 暂时只做前端模拟
const newApiKey = {
id: Date.now(),
user_id: createForm.value.user_id,
key: `sk-${Math.random().toString(36).substring(2, 42)}`,
name: createForm.value.name,
expires_at: createForm.value.expires_at,
is_active: true,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
}
apiKeys.value.push(newApiKey)
createDialogVisible.value = false
}
const toggleApiKey = async (id: number, isActive: boolean) => {
// 这里应该调用后端API更新API密钥状态
// 暂时只做前端模拟
const index = apiKeys.value.findIndex(key => key.id === id)
if (index !== -1) {
apiKeys.value[index].is_active = isActive
apiKeys.value[index].updated_at = new Date().toISOString()
}
}
const deleteApiKey = async (id: number) => {
// 这里应该调用后端API删除API密钥
// 暂时只做前端模拟
apiKeys.value = apiKeys.value.filter(key => key.id !== id)
}
</script>
<style scoped>
.admin-api-keys {
padding: 20px;
}
.admin-api-keys h2 {
font-size: 20px;
margin-bottom: 20px;
color: #333;
}
.api-keys-actions {
margin-bottom: 20px;
}
.api-keys-table {
margin-top: 20px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
@media (max-width: 768px) {
.admin-api-keys {
padding: 10px;
}
.api-keys-actions {
margin-bottom: 10px;
}
.api-keys-table {
margin-top: 10px;
}
}
</style>

View File

@@ -50,32 +50,31 @@
<div class="option-content"> <div class="option-content">
<div class="option-name">{{ repo.name }}</div> <div class="option-name">{{ repo.name }}</div>
<div class="option-desc">{{ repo.description || '无描述' }}</div> <div class="option-desc">{{ repo.description || '无描述' }}</div>
<div class="option-url">{{ repo.repo_url || '无仓库地址' }}</div>
</div> </div>
</el-option> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<!-- 算法选择 --> <!-- 仓库信息 -->
<el-form-item label="算法" prop="algorithm_id"> <el-form-item label="仓库描述">
<el-select <el-input
v-model="serviceForm.algorithm_id" v-model="serviceForm.repository_description"
placeholder="请选择算法" type="textarea"
@change="onAlgorithmChange" placeholder="仓库描述"
rows="2"
class="w-full" class="w-full"
:disabled="!serviceForm.repository_id" :disabled="true"
> />
<el-option </el-form-item>
v-for="algorithm in algorithms"
:key="algorithm.id" <el-form-item label="仓库地址">
:label="algorithm.name" <el-input
:value="algorithm.id" v-model="serviceForm.repository_url"
> placeholder="仓库地址"
<div class="option-content"> class="w-full"
<div class="option-name">{{ algorithm.name }}</div> :disabled="true"
<div class="option-desc">{{ algorithm.description || '无描述' }}</div> />
</div>
</el-option>
</el-select>
</el-form-item> </el-form-item>
<!-- 服务基本信息 --> <!-- 服务基本信息 -->
@@ -271,6 +270,7 @@ import { ref, reactive, onMounted } from 'vue'
import { Check, Refresh } from '@element-plus/icons-vue' import { Check, Refresh } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import axios from 'axios'
// 路由 // 路由
const router = useRouter() const router = useRouter()
@@ -281,14 +281,14 @@ const serviceFormRef = ref()
// 加载状态 // 加载状态
const submitLoading = ref(false) const submitLoading = ref(false)
// 仓库和算法列表 // 仓库列表
const repositories = ref<any[]>([]) const repositories = ref<any[]>([])
const algorithms = ref<any[]>([])
// 服务表单 // 服务表单
const serviceForm = reactive({ const serviceForm = reactive({
repository_id: '', repository_id: '',
algorithm_id: '', repository_description: '',
repository_url: '',
name: '', name: '',
version: '1.0.0', version: '1.0.0',
description: '', description: '',
@@ -308,9 +308,6 @@ const rules = {
repository_id: [ repository_id: [
{ required: true, message: '请选择算法仓库', trigger: 'blur' } { required: true, message: '请选择算法仓库', trigger: 'blur' }
], ],
algorithm_id: [
{ required: true, message: '请选择算法', trigger: 'blur' }
],
name: [ name: [
{ required: true, message: '请输入服务名称', trigger: 'blur' }, { required: true, message: '请输入服务名称', trigger: 'blur' },
{ min: 2, max: 50, message: '服务名称长度应在 2-50 个字符之间', trigger: 'blur' } { min: 2, max: 50, message: '服务名称长度应在 2-50 个字符之间', trigger: 'blur' }
@@ -343,93 +340,60 @@ const registrationResult = reactive({
// 加载仓库列表 // 加载仓库列表
const loadRepositories = async () => { const loadRepositories = async () => {
try { try {
// 这里应该调用后端API获取仓库列表 // 调用后端API获取仓库列表
// 暂时使用模拟数据 const response = await axios.get('/api/repositories')
if (response.data.success) {
repositories.value = response.data.repositories
console.log('仓库列表加载完成:', repositories.value)
} else {
throw new Error('Failed to load repositories')
}
} catch (error) {
console.error('加载仓库列表失败:', error)
ElMessage.error('加载仓库列表失败')
// 使用备用模拟数据
repositories.value = [ repositories.value = [
{ {
id: 'repo-001', id: 'repo-001',
name: '图像分类算法', name: '图像分类算法',
description: '基于ResNet的图像分类算法仓库', description: '基于ResNet的图像分类算法仓库',
type: 'python', type: 'python',
status: 'active' status: 'active',
repo_url: 'https://github.com/example/image-classification'
}, },
{ {
id: 'repo-002', id: 'repo-002',
name: '文本分类算法', name: '文本分类算法',
description: '基于BERT的文本分类算法仓库', description: '基于BERT的文本分类算法仓库',
type: 'python', type: 'python',
status: 'active' status: 'active',
}, repo_url: 'https://github.com/example/text-classification'
{
id: 'repo-003',
name: '目标检测算法',
description: '基于YOLOv5的目标检测算法仓库',
type: 'python',
status: 'active'
},
{
id: 'repo-004',
name: '推荐系统算法',
description: '基于协同过滤的推荐系统算法仓库',
type: 'nodejs',
status: 'active'
} }
] ]
console.log('仓库列表加载完成')
} catch (error) {
console.error('加载仓库列表失败:', error)
ElMessage.error('加载仓库列表失败')
}
}
// 加载算法列表
const loadAlgorithms = async (repositoryId: string) => {
try {
// 这里应该根据仓库ID调用后端API获取算法列表
// 暂时使用模拟数据
algorithms.value = [
{
id: 'algo-001',
name: 'ResNet图像分类',
description: '使用ResNet50模型进行图像分类',
repository_id: repositoryId,
entry_point: 'model/predict.py'
},
{
id: 'algo-002',
name: 'MobileNet轻量分类',
description: '使用MobileNet进行轻量级图像分类',
repository_id: repositoryId,
entry_point: 'model/mobilenet_predict.py'
}
]
console.log('算法列表加载完成')
} catch (error) {
console.error('加载算法列表失败:', error)
ElMessage.error('加载算法列表失败')
} }
} }
// 仓库选择变化 // 仓库选择变化
const onRepositoryChange = (repositoryId: string) => { const onRepositoryChange = (repositoryId: string) => {
if (repositoryId) { if (repositoryId) {
algorithms.value = [] // 从选择的仓库中带出默认值
serviceForm.algorithm_id = '' const selectedRepo = repositories.value.find(r => r.id === repositoryId)
loadAlgorithms(repositoryId) if (selectedRepo) {
} // 填充仓库描述和地址
serviceForm.repository_description = selectedRepo.description || ''
serviceForm.repository_url = selectedRepo.repo_url || ''
// 自动填充服务描述
if (!serviceForm.description) {
serviceForm.description = selectedRepo.description || ''
} }
// 算法选择变化 console.log('从仓库中带出默认值:', selectedRepo)
const onAlgorithmChange = (algorithmId: string) => {
if (algorithmId) {
// 可以根据算法ID自动填充一些默认值
const selectedAlgorithm = algorithms.value.find(a => a.id === algorithmId)
if (selectedAlgorithm) {
// 自动生成服务名称
if (!serviceForm.name) {
serviceForm.name = `${selectedAlgorithm.name}服务`
}
} }
} else {
// 清空仓库信息
serviceForm.repository_description = ''
serviceForm.repository_url = ''
} }
} }
@@ -460,29 +424,24 @@ const submitForm = async () => {
console.log('提交服务注册请求:', { console.log('提交服务注册请求:', {
repository_id: serviceForm.repository_id, repository_id: serviceForm.repository_id,
algorithm_id: serviceForm.algorithm_id, repository_description: serviceForm.repository_description,
repository_url: serviceForm.repository_url,
service_config: serviceConfig service_config: serviceConfig
}) })
// 这里应该调用后端API注册服务 // 调用后端API注册服务
// 暂时使用模拟数据 const response = await axios.post('/api/services/register', {
await new Promise(resolve => setTimeout(resolve, 3000)) repository_id: serviceForm.repository_id,
// 模拟注册结果
const mockService = {
service_id: `service-${Date.now()}`,
name: serviceForm.name, name: serviceForm.name,
version: serviceForm.version, version: serviceForm.version,
description: serviceForm.description, service_type: serviceForm.service_type,
status: 'running',
host: serviceForm.host, host: serviceForm.host,
port: serviceForm.port, port: serviceForm.port,
api_url: `http://${serviceForm.host}:${serviceForm.port}`, timeout: serviceForm.timeout,
algorithm_id: serviceForm.algorithm_id, health_check_path: serviceForm.health_check_path
repository_id: serviceForm.repository_id, })
created_at: new Date().toISOString(),
last_heartbeat: new Date().toISOString() const mockService = response.data.service
}
// 更新注册结果 // 更新注册结果
registrationResult.success = true registrationResult.success = true
@@ -511,8 +470,6 @@ const resetForm = () => {
if (serviceFormRef.value) { if (serviceFormRef.value) {
serviceFormRef.value.resetFields() serviceFormRef.value.resetFields()
} }
// 重置算法列表
algorithms.value = []
// 重置默认值 // 重置默认值
serviceForm.version = '1.0.0' serviceForm.version = '1.0.0'
serviceForm.service_type = 'http' serviceForm.service_type = 'http'
@@ -524,6 +481,8 @@ const resetForm = () => {
serviceForm.replicas = 1 serviceForm.replicas = 1
serviceForm.health_check_path = '/health' serviceForm.health_check_path = '/health'
serviceForm.health_check_interval = 30 serviceForm.health_check_interval = 30
serviceForm.repository_description = ''
serviceForm.repository_url = ''
} }
// 跳转到服务管理页面 // 跳转到服务管理页面
@@ -582,6 +541,14 @@ onMounted(async () => {
line-height: 1.4; line-height: 1.4;
} }
.option-url {
font-size: 11px;
color: #666;
line-height: 1.3;
margin-top: 2px;
word-break: break-all;
}
.form-actions { .form-actions {
display: flex; display: flex;
gap: 10px; gap: 10px;

View File

@@ -10,7 +10,7 @@
<el-table-column prop="email" label="邮箱" /> <el-table-column prop="email" label="邮箱" />
<el-table-column prop="role" label="角色" width="120"> <el-table-column prop="role" label="角色" width="120">
<template #default="scope"> <template #default="scope">
<el-tag :type="getRoleType(scope.row.role)">{{ scope.row.role }}</el-tag> <el-tag :type="getRoleType(scope.row.role?.name || scope.row.role)">{{ scope.row.role?.name || scope.row.role }}</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="created_at" label="创建时间" width="180"> <el-table-column prop="created_at" label="创建时间" width="180">
@@ -38,11 +38,12 @@
<el-form-item label="密码" required> <el-form-item label="密码" required>
<el-input v-model="createForm.password" type="password" placeholder="请输入密码" /> <el-input v-model="createForm.password" type="password" placeholder="请输入密码" />
</el-form-item> </el-form-item>
<el-form-item label="确认密码" required>
<el-input v-model="createForm.confirmPassword" type="password" placeholder="请再次输入密码" />
</el-form-item>
<el-form-item label="角色" required> <el-form-item label="角色" required>
<el-select v-model="createForm.role" placeholder="请选择角色"> <el-select v-model="createForm.role_id" placeholder="请选择角色">
<el-option label="管理员" value="admin" /> <el-option v-for="role in roles" :key="role.id" :label="role.name" :value="role.id" />
<el-option label="用户" value="user" />
<el-option label="访客" value="guest" />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-form> </el-form>
@@ -66,11 +67,12 @@
<el-form-item label="密码"> <el-form-item label="密码">
<el-input v-model="editForm.password" type="password" placeholder="请输入密码(留空表示不修改)" /> <el-input v-model="editForm.password" type="password" placeholder="请输入密码(留空表示不修改)" />
</el-form-item> </el-form-item>
<el-form-item label="确认密码">
<el-input v-model="editForm.confirmPassword" type="password" placeholder="请再次输入密码(留空表示不修改)" />
</el-form-item>
<el-form-item label="角色" required> <el-form-item label="角色" required>
<el-select v-model="editForm.role" placeholder="请选择角色"> <el-select v-model="editForm.role_id" placeholder="请选择角色">
<el-option label="管理员" value="admin" /> <el-option v-for="role in roles" :key="role.id" :label="role.name" :value="role.id" />
<el-option label="用户" value="user" />
<el-option label="访客" value="guest" />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-form> </el-form>
@@ -85,41 +87,32 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref, onMounted } from 'vue'
import axios from 'axios'
const users = ref<any[]>([ const users = ref<any[]>([])
{ const loading = ref(false)
id: 1, const error = ref('')
username: 'admin',
email: 'admin@example.com',
role: 'admin',
created_at: new Date().toISOString()
},
{
id: 2,
username: 'user1',
email: 'user1@example.com',
role: 'user',
created_at: new Date().toISOString()
}
])
const createDialogVisible = ref(false) const createDialogVisible = ref(false)
const editDialogVisible = ref(false) const editDialogVisible = ref(false)
const roles = ref<any[]>([])
const createForm = ref({ const createForm = ref({
username: '', username: '',
email: '', email: '',
password: '', password: '',
role: 'user' confirmPassword: '',
role_id: ''
}) })
const editForm = ref({ const editForm = ref({
id: 0, id: '',
username: '', username: '',
email: '', email: '',
password: '', password: '',
role: 'user' confirmPassword: '',
role_id: ''
}) })
const formatDate = (dateString?: string) => { const formatDate = (dateString?: string) => {
@@ -134,19 +127,110 @@ const getRoleType = (role?: string) => {
return 'danger' return 'danger'
case 'user': case 'user':
return 'primary' return 'primary'
case 'guest':
return 'info'
default: default:
return 'default' return 'default'
} }
} }
// 获取角色列表
const fetchRoles = async () => {
try {
const response = await axios.get('/api/users/roles')
roles.value = response.data
} catch (err) {
console.error('获取角色列表失败:', err)
}
}
// 获取用户列表
const fetchUsers = async () => {
loading.value = true
error.value = ''
try {
const response = await axios.get('/api/users/')
users.value = response.data.users
} catch (err) {
console.error('获取用户列表失败:', err)
error.value = '获取用户列表失败'
} finally {
loading.value = false
}
}
// 创建用户
const createUser = async () => {
// 验证密码一致性
if (createForm.value.password !== createForm.value.confirmPassword) {
error.value = '两次输入的密码不一致'
return
}
loading.value = true
error.value = ''
try {
// 移除confirmPassword字段因为后端API不需要
const { confirmPassword, ...userData } = createForm.value
await axios.post('/api/users/register', userData)
await fetchUsers()
createDialogVisible.value = false
} catch (err) {
console.error('创建用户失败:', err)
error.value = '创建用户失败'
} finally {
loading.value = false
}
}
// 更新用户
const updateUser = async () => {
// 验证密码一致性(如果密码不为空)
if (editForm.value.password !== '' && editForm.value.password !== editForm.value.confirmPassword) {
error.value = '两次输入的密码不一致'
return
}
loading.value = true
error.value = ''
try {
const updateData = { ...editForm.value }
// 移除不需要的字段
if (!updateData.password) {
delete updateData.password
}
delete updateData.confirmPassword
await axios.put(`/api/users/${editForm.value.id}`, updateData)
await fetchUsers()
editDialogVisible.value = false
} catch (err) {
console.error('更新用户失败:', err)
error.value = '更新用户失败'
} finally {
loading.value = false
}
}
// 删除用户
const deleteUser = async (id: string) => {
loading.value = true
error.value = ''
try {
await axios.delete(`/api/users/${id}`)
await fetchUsers()
} catch (err) {
console.error('删除用户失败:', err)
error.value = '删除用户失败'
} finally {
loading.value = false
}
}
const showCreateDialog = () => { const showCreateDialog = () => {
createForm.value = { createForm.value = {
username: '', username: '',
email: '', email: '',
password: '', password: '',
role: 'user' confirmPassword: '',
role_id: roles.value.length > 0 ? roles.value.find(r => r.name === 'user')?.id || roles.value[0].id : ''
} }
createDialogVisible.value = true createDialogVisible.value = true
} }
@@ -157,47 +241,17 @@ const showEditDialog = (user: any) => {
username: user.username, username: user.username,
email: user.email, email: user.email,
password: '', password: '',
role: user.role confirmPassword: '',
role_id: user.role_id || user.role?.id || ''
} }
editDialogVisible.value = true editDialogVisible.value = true
} }
const createUser = async () => { // 组件挂载时获取用户列表和角色列表
// 这里应该调用后端API创建用户 onMounted(async () => {
// 暂时只做前端模拟 await fetchRoles()
const newUser = { await fetchUsers()
id: Date.now(), })
...createForm.value,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
}
users.value.push(newUser)
createDialogVisible.value = false
}
const updateUser = async () => {
// 这里应该调用后端API更新用户
// 暂时只做前端模拟
const index = users.value.findIndex(u => u.id === editForm.value.id)
if (index !== -1) {
const updateData = { ...editForm.value }
if (!updateData.password) {
delete updateData.password
}
users.value[index] = {
...users.value[index],
...updateData,
updated_at: new Date().toISOString()
}
}
editDialogVisible.value = false
}
const deleteUser = async (id: number) => {
// 这里应该调用后端API删除用户
// 暂时只做前端模拟
users.value = users.value.filter(u => u.id !== id)
}
</script> </script>
<style scoped> <style scoped>