- 从服务器拉取完整代码 - 按框架规范整理项目结构 - 配置 Drone CI 测试环境部署 - 包含后端(FastAPI)、前端(Vue3)、管理端 技术栈: Vue3 + TypeScript + FastAPI + MySQL
146 lines
4.1 KiB
Python
146 lines
4.1 KiB
Python
"""
|
||
播课功能 API 接口
|
||
"""
|
||
|
||
import logging
|
||
from datetime import datetime
|
||
from typing import Optional
|
||
|
||
from fastapi import APIRouter, Depends, HTTPException
|
||
from pydantic import BaseModel, Field
|
||
from sqlalchemy import select
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from app.core.deps import get_db, get_current_user, require_admin_or_manager
|
||
from app.schemas.base import ResponseModel
|
||
from app.models.course import Course
|
||
from app.models.user import User
|
||
from app.services.coze_broadcast_service import broadcast_service
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
router = APIRouter()
|
||
|
||
|
||
# Schema 定义
|
||
class GenerateBroadcastResponse(BaseModel):
|
||
"""生成播课响应"""
|
||
message: str = Field(..., description="提示信息")
|
||
|
||
|
||
class BroadcastInfo(BaseModel):
|
||
"""播课信息"""
|
||
has_broadcast: bool = Field(..., description="是否有播课")
|
||
mp3_url: Optional[str] = Field(None, description="播课音频URL")
|
||
generated_at: Optional[datetime] = Field(None, description="生成时间")
|
||
|
||
|
||
@router.post("/courses/{course_id}/generate-broadcast", response_model=ResponseModel[GenerateBroadcastResponse])
|
||
async def generate_broadcast(
|
||
course_id: int,
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(require_admin_or_manager)
|
||
):
|
||
"""
|
||
触发播课音频生成(立即返回,Coze工作流会直接写数据库)
|
||
|
||
权限:manager、admin
|
||
|
||
Args:
|
||
course_id: 课程ID
|
||
db: 数据库会话
|
||
current_user: 当前用户
|
||
|
||
Returns:
|
||
启动提示信息
|
||
|
||
Raises:
|
||
HTTPException 404: 课程不存在
|
||
"""
|
||
logger.info(
|
||
f"请求生成播课",
|
||
extra={"course_id": course_id, "user_id": current_user.id}
|
||
)
|
||
|
||
# 查询课程
|
||
result = await db.execute(
|
||
select(Course)
|
||
.where(Course.id == course_id)
|
||
.where(Course.is_deleted == False)
|
||
)
|
||
course = result.scalar_one_or_none()
|
||
|
||
if not course:
|
||
logger.warning(f"课程不存在", extra={"course_id": course_id})
|
||
raise HTTPException(status_code=404, detail="课程不存在")
|
||
|
||
# 调用 Coze 工作流(不等待结果,工作流会直接写数据库)
|
||
try:
|
||
await broadcast_service.trigger_workflow(course_id)
|
||
|
||
logger.info(
|
||
f"播课生成工作流已触发",
|
||
extra={"course_id": course_id, "user_id": current_user.id}
|
||
)
|
||
|
||
return ResponseModel(
|
||
code=200,
|
||
message="播课生成已启动",
|
||
data=GenerateBroadcastResponse(
|
||
message="播课生成工作流已启动,生成完成后将自动更新"
|
||
)
|
||
)
|
||
except Exception as e:
|
||
logger.error(
|
||
f"触发播课生成失败",
|
||
extra={"course_id": course_id, "error": str(e)}
|
||
)
|
||
raise HTTPException(status_code=500, detail=f"触发播课生成失败: {str(e)}")
|
||
|
||
|
||
@router.get("/courses/{course_id}/broadcast", response_model=ResponseModel[BroadcastInfo])
|
||
async def get_broadcast_info(
|
||
course_id: int,
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_current_user)
|
||
):
|
||
"""
|
||
获取播课信息
|
||
|
||
权限:所有登录用户
|
||
|
||
Args:
|
||
course_id: 课程ID
|
||
db: 数据库会话
|
||
current_user: 当前用户
|
||
|
||
Returns:
|
||
播课信息
|
||
|
||
Raises:
|
||
HTTPException 404: 课程不存在
|
||
"""
|
||
# 查询课程
|
||
result = await db.execute(
|
||
select(Course)
|
||
.where(Course.id == course_id)
|
||
.where(Course.is_deleted == False)
|
||
)
|
||
course = result.scalar_one_or_none()
|
||
|
||
if not course:
|
||
raise HTTPException(status_code=404, detail="课程不存在")
|
||
|
||
# 构建播课信息
|
||
has_broadcast = bool(course.broadcast_audio_url)
|
||
|
||
return ResponseModel(
|
||
code=200,
|
||
message="success",
|
||
data=BroadcastInfo(
|
||
has_broadcast=has_broadcast,
|
||
mp3_url=course.broadcast_audio_url if has_broadcast else None,
|
||
generated_at=course.broadcast_generated_at if has_broadcast else None
|
||
)
|
||
)
|