Files
algorithm/backend/app/routes/services.py
2026-02-09 23:59:25 +08:00

1068 lines
34 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""算法服务管理路由,提供服务注册、管理等功能"""
from fastapi import APIRouter, HTTPException, status, Depends
from typing import List, Dict, Any, Optional
from pydantic import BaseModel
import uuid
import os
from app.config.settings import settings
from app.models.models import AlgorithmService, ServiceGroup, AlgorithmRepository
from app.models.database import SessionLocal
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.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: Optional[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]]
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()
service_generator = ServiceGenerator()
service_orchestrator = ServiceOrchestrator(deployment_mode=settings.DEPLOYMENT_MODE)
@router.post("/register", status_code=status.HTTP_201_CREATED)
async def register_service(
request: RegisterServiceRequest,
current_user: UserResponse = Depends(get_current_active_user)
):
"""注册新服务"""
# 检查用户权限
print(f"用户角色: {current_user.role_name}")
print(f"用户角色对象: {current_user.role}")
if not hasattr(current_user, 'role_name') or current_user.role_name != "admin":
raise HTTPException(status_code=403, detail="Insufficient permissions")
# 创建数据库会话
db = SessionLocal()
try:
# 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. 分析项目
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=repo.name, # 使用仓库名称作为算法名称
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() if new_service.updated_at else None
}
}
finally:
db.close()
@router.get("", response_model=ServiceListResponse)
async def list_services(
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:
# 查询服务列表
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() if service.updated_at else None
))
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: UserResponse = Depends(get_current_active_user)
):
"""获取服务详情"""
# 检查用户权限
if current_user.role_name != "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() if service.updated_at else None
)
)
finally:
db.close()
@router.post("/{service_id}/start")
async def start_service(
service_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:
# 查询服务
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: UserResponse = Depends(get_current_active_user)
):
"""停止服务"""
# 检查用户权限
if current_user.role_name != "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: UserResponse = Depends(get_current_active_user)
):
"""重启服务"""
# 检查用户权限
if current_user.role_name != "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: UserResponse = Depends(get_current_active_user)
):
"""删除服务"""
# 检查用户权限
if current_user.role_name != "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: UserResponse = Depends(get_current_active_user)
):
"""获取服务状态"""
# 检查用户权限
if current_user.role_name != "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: UserResponse = Depends(get_current_active_user)
):
"""获取服务日志"""
# 检查用户权限
if current_user.role_name != "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: UserResponse = Depends(get_current_active_user)
):
"""获取仓库中的算法列表"""
# 检查用户权限
if current_user.role_name != "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))
# 服务分组管理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()