- 从服务器拉取完整代码 - 按框架规范整理项目结构 - 配置 Drone CI 测试环境部署 - 包含后端(FastAPI)、前端(Vue3)、管理端 技术栈: Vue3 + TypeScript + FastAPI + MySQL
787 lines
25 KiB
Python
787 lines
25 KiB
Python
"""
|
||
课程管理API路由
|
||
"""
|
||
from typing import List, Optional
|
||
|
||
from fastapi import APIRouter, Depends, Query, status, BackgroundTasks, Request
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from app.core.deps import get_db, get_current_user, require_admin, require_admin_or_manager, User
|
||
from app.core.exceptions import NotFoundError, BadRequestError
|
||
from app.core.logger import get_logger
|
||
from app.services.system_log_service import system_log_service
|
||
from app.schemas.system_log import SystemLogCreate
|
||
from app.models.course import CourseStatus, CourseCategory
|
||
from app.schemas.base import ResponseModel, PaginationParams, PaginatedResponse
|
||
from app.schemas.course import (
|
||
CourseCreate,
|
||
CourseUpdate,
|
||
CourseInDB,
|
||
CourseList,
|
||
CourseMaterialCreate,
|
||
CourseMaterialInDB,
|
||
KnowledgePointCreate,
|
||
KnowledgePointUpdate,
|
||
KnowledgePointInDB,
|
||
GrowthPathCreate,
|
||
GrowthPathInDB,
|
||
CourseExamSettingsCreate,
|
||
CourseExamSettingsUpdate,
|
||
CourseExamSettingsInDB,
|
||
CoursePositionAssignment,
|
||
CoursePositionAssignmentInDB,
|
||
)
|
||
from app.services.course_service import (
|
||
course_service,
|
||
knowledge_point_service,
|
||
growth_path_service,
|
||
)
|
||
|
||
logger = get_logger(__name__)
|
||
router = APIRouter(prefix="/courses", tags=["courses"])
|
||
|
||
|
||
@router.get("", response_model=ResponseModel[PaginatedResponse[CourseInDB]])
|
||
async def get_courses(
|
||
page: int = Query(1, ge=1, description="页码"),
|
||
size: int = Query(20, ge=1, le=100, description="每页数量"),
|
||
status: Optional[CourseStatus] = Query(None, description="课程状态"),
|
||
category: Optional[CourseCategory] = Query(None, description="课程分类"),
|
||
is_featured: Optional[bool] = Query(None, description="是否推荐"),
|
||
keyword: Optional[str] = Query(None, description="搜索关键词"),
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""
|
||
获取课程列表(支持分页和筛选)
|
||
|
||
- **page**: 页码
|
||
- **size**: 每页数量
|
||
- **status**: 课程状态筛选
|
||
- **category**: 课程分类筛选
|
||
- **is_featured**: 是否推荐筛选
|
||
- **keyword**: 关键词搜索(搜索名称和描述)
|
||
"""
|
||
page_params = PaginationParams(page=page, page_size=size)
|
||
filters = CourseList(
|
||
status=status, category=category, is_featured=is_featured, keyword=keyword
|
||
)
|
||
|
||
result = await course_service.get_course_list(
|
||
db, page_params=page_params, filters=filters, user_id=current_user.id
|
||
)
|
||
|
||
return ResponseModel(data=result, message="获取课程列表成功")
|
||
|
||
|
||
@router.post(
|
||
"", response_model=ResponseModel[CourseInDB], status_code=status.HTTP_201_CREATED
|
||
)
|
||
async def create_course(
|
||
course_in: CourseCreate,
|
||
request: Request,
|
||
current_user: User = Depends(require_admin),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""
|
||
创建课程(需要管理员权限)
|
||
|
||
- **name**: 课程名称
|
||
- **description**: 课程描述
|
||
- **category**: 课程分类
|
||
- **status**: 课程状态(默认为草稿)
|
||
- **cover_image**: 封面图片URL
|
||
- **duration_hours**: 课程时长(小时)
|
||
- **difficulty_level**: 难度等级(1-5)
|
||
- **tags**: 标签列表
|
||
- **is_featured**: 是否推荐
|
||
"""
|
||
course = await course_service.create_course(
|
||
db, course_in=course_in, created_by=current_user.id
|
||
)
|
||
|
||
# 记录课程创建日志
|
||
await system_log_service.create_log(
|
||
db,
|
||
SystemLogCreate(
|
||
level="INFO",
|
||
type="api",
|
||
message=f"创建课程: {course.name}",
|
||
user_id=current_user.id,
|
||
user=current_user.username,
|
||
ip=request.client.host if request.client else None,
|
||
path="/api/v1/courses",
|
||
method="POST",
|
||
user_agent=request.headers.get("user-agent")
|
||
)
|
||
)
|
||
|
||
return ResponseModel(data=course, message="创建课程成功")
|
||
|
||
|
||
@router.get("/{course_id}", response_model=ResponseModel[CourseInDB])
|
||
async def get_course(
|
||
course_id: int,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""
|
||
获取课程详情
|
||
|
||
- **course_id**: 课程ID
|
||
"""
|
||
course = await course_service.get_by_id(db, course_id)
|
||
if not course:
|
||
raise NotFoundError(f"课程ID {course_id} 不存在")
|
||
|
||
logger.info(f"查看课程详情 - course_id: {course_id}, user_id: {current_user.id}")
|
||
|
||
return ResponseModel(data=course, message="获取课程详情成功")
|
||
|
||
|
||
@router.put("/{course_id}", response_model=ResponseModel[CourseInDB])
|
||
async def update_course(
|
||
course_id: int,
|
||
course_in: CourseUpdate,
|
||
current_user: User = Depends(require_admin),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""
|
||
更新课程(需要管理员权限)
|
||
|
||
- **course_id**: 课程ID
|
||
- **course_in**: 更新的课程数据(所有字段都是可选的)
|
||
"""
|
||
course = await course_service.update_course(
|
||
db, course_id=course_id, course_in=course_in, updated_by=current_user.id
|
||
)
|
||
|
||
return ResponseModel(data=course, message="更新课程成功")
|
||
|
||
|
||
@router.delete("/{course_id}", response_model=ResponseModel[bool])
|
||
async def delete_course(
|
||
course_id: int,
|
||
request: Request,
|
||
current_user: User = Depends(require_admin_or_manager),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""
|
||
删除课程(需要管理员权限)
|
||
|
||
- **course_id**: 课程ID
|
||
|
||
说明:任意状态均可软删除(is_deleted=1),请谨慎操作
|
||
"""
|
||
# 先获取课程信息
|
||
course = await course_service.get_by_id(db, course_id)
|
||
course_name = course.name if course else f"ID:{course_id}"
|
||
|
||
success = await course_service.delete_course(
|
||
db, course_id=course_id, deleted_by=current_user.id
|
||
)
|
||
|
||
# 记录课程删除日志
|
||
if success:
|
||
await system_log_service.create_log(
|
||
db,
|
||
SystemLogCreate(
|
||
level="INFO",
|
||
type="api",
|
||
message=f"删除课程: {course_name}",
|
||
user_id=current_user.id,
|
||
user=current_user.username,
|
||
ip=request.client.host if request.client else None,
|
||
path=f"/api/v1/courses/{course_id}",
|
||
method="DELETE",
|
||
user_agent=request.headers.get("user-agent")
|
||
)
|
||
)
|
||
|
||
return ResponseModel(data=success, message="删除课程成功" if success else "删除课程失败")
|
||
|
||
|
||
# 课程资料相关API
|
||
@router.post(
|
||
"/{course_id}/materials",
|
||
response_model=ResponseModel[CourseMaterialInDB],
|
||
status_code=status.HTTP_201_CREATED,
|
||
)
|
||
async def add_course_material(
|
||
course_id: int,
|
||
material_in: CourseMaterialCreate,
|
||
background_tasks: BackgroundTasks,
|
||
current_user: User = Depends(require_admin),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""
|
||
添加课程资料(需要管理员权限)
|
||
|
||
- **course_id**: 课程ID
|
||
- **name**: 资料名称
|
||
- **description**: 资料描述
|
||
- **file_url**: 文件URL
|
||
- **file_type**: 文件类型(pdf, doc, docx, ppt, pptx, xls, xlsx, mp4, mp3, zip)
|
||
- **file_size**: 文件大小(字节)
|
||
|
||
添加资料后会自动触发知识点分析
|
||
"""
|
||
material = await course_service.add_course_material(
|
||
db, course_id=course_id, material_in=material_in, created_by=current_user.id
|
||
)
|
||
|
||
# 获取课程信息用于知识点分析
|
||
course = await course_service.get_by_id(db, course_id)
|
||
if course:
|
||
# 异步触发知识点分析
|
||
from app.services.ai.knowledge_analysis_v2 import knowledge_analysis_service_v2
|
||
background_tasks.add_task(
|
||
_trigger_knowledge_analysis,
|
||
db,
|
||
course_id,
|
||
material.id,
|
||
material.file_url,
|
||
course.name,
|
||
current_user.id
|
||
)
|
||
|
||
logger.info(
|
||
f"资料添加成功,已触发知识点分析 - course_id: {course_id}, material_id: {material.id}, user_id: {current_user.id}"
|
||
)
|
||
|
||
return ResponseModel(data=material, message="添加课程资料成功")
|
||
|
||
|
||
@router.get(
|
||
"/{course_id}/materials",
|
||
response_model=ResponseModel[List[CourseMaterialInDB]],
|
||
)
|
||
async def list_course_materials(
|
||
course_id: int,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""
|
||
获取课程资料列表
|
||
|
||
- **course_id**: 课程ID
|
||
"""
|
||
materials = await course_service.get_course_materials(db, course_id=course_id)
|
||
return ResponseModel(data=materials, message="获取课程资料列表成功")
|
||
|
||
|
||
@router.delete(
|
||
"/{course_id}/materials/{material_id}",
|
||
response_model=ResponseModel[bool],
|
||
)
|
||
async def delete_course_material(
|
||
course_id: int,
|
||
material_id: int,
|
||
current_user: User = Depends(require_admin),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""
|
||
删除课程资料(需要管理员权限)
|
||
|
||
- **course_id**: 课程ID
|
||
- **material_id**: 资料ID
|
||
"""
|
||
success = await course_service.delete_course_material(
|
||
db, course_id=course_id, material_id=material_id, deleted_by=current_user.id
|
||
)
|
||
return ResponseModel(data=success, message="删除课程资料成功" if success else "删除课程资料失败")
|
||
|
||
|
||
# 知识点相关API
|
||
@router.get(
|
||
"/{course_id}/knowledge-points",
|
||
response_model=ResponseModel[List[KnowledgePointInDB]],
|
||
)
|
||
async def get_course_knowledge_points(
|
||
course_id: int,
|
||
material_id: Optional[int] = Query(None, description="资料ID"),
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""
|
||
获取课程的知识点列表
|
||
|
||
- **course_id**: 课程ID
|
||
- **material_id**: 资料ID(可选,用于筛选特定资料的知识点)
|
||
"""
|
||
# 先检查课程是否存在
|
||
course = await course_service.get_by_id(db, course_id)
|
||
if not course:
|
||
raise NotFoundError(f"课程ID {course_id} 不存在")
|
||
|
||
knowledge_points = await knowledge_point_service.get_knowledge_points_by_course(
|
||
db, course_id=course_id, material_id=material_id
|
||
)
|
||
|
||
return ResponseModel(data=knowledge_points, message="获取知识点列表成功")
|
||
|
||
|
||
@router.post(
|
||
"/{course_id}/knowledge-points",
|
||
response_model=ResponseModel[KnowledgePointInDB],
|
||
status_code=status.HTTP_201_CREATED,
|
||
)
|
||
async def create_knowledge_point(
|
||
course_id: int,
|
||
point_in: KnowledgePointCreate,
|
||
current_user: User = Depends(require_admin),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""
|
||
创建知识点(需要管理员权限)
|
||
|
||
- **course_id**: 课程ID
|
||
- **name**: 知识点名称
|
||
- **description**: 知识点描述
|
||
- **parent_id**: 父知识点ID
|
||
- **weight**: 权重(0-10)
|
||
- **is_required**: 是否必修
|
||
- **estimated_hours**: 预计学习时间(小时)
|
||
"""
|
||
knowledge_point = await knowledge_point_service.create_knowledge_point(
|
||
db, course_id=course_id, point_in=point_in, created_by=current_user.id
|
||
)
|
||
|
||
return ResponseModel(data=knowledge_point, message="创建知识点成功")
|
||
|
||
|
||
@router.put(
|
||
"/knowledge-points/{point_id}", response_model=ResponseModel[KnowledgePointInDB]
|
||
)
|
||
async def update_knowledge_point(
|
||
point_id: int,
|
||
point_in: KnowledgePointUpdate,
|
||
current_user: User = Depends(require_admin),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""
|
||
更新知识点(需要管理员权限)
|
||
|
||
- **point_id**: 知识点ID
|
||
- **point_in**: 更新的知识点数据(所有字段都是可选的)
|
||
"""
|
||
knowledge_point = await knowledge_point_service.update_knowledge_point(
|
||
db, point_id=point_id, point_in=point_in, updated_by=current_user.id
|
||
)
|
||
|
||
return ResponseModel(data=knowledge_point, message="更新知识点成功")
|
||
|
||
|
||
@router.delete("/knowledge-points/{point_id}", response_model=ResponseModel[bool])
|
||
async def delete_knowledge_point(
|
||
point_id: int,
|
||
current_user: User = Depends(require_admin),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""
|
||
删除知识点(需要管理员权限)
|
||
|
||
- **point_id**: 知识点ID
|
||
"""
|
||
success = await knowledge_point_service.delete(
|
||
db, id=point_id, soft=True, deleted_by=current_user.id
|
||
)
|
||
|
||
if success:
|
||
logger.warning("删除知识点", knowledge_point_id=point_id, deleted_by=current_user.id)
|
||
|
||
return ResponseModel(data=success, message="删除知识点成功" if success else "删除知识点失败")
|
||
|
||
|
||
# 资料知识点关联API
|
||
@router.get(
|
||
"/materials/{material_id}/knowledge-points",
|
||
response_model=ResponseModel[List[KnowledgePointInDB]],
|
||
)
|
||
async def get_material_knowledge_points(
|
||
material_id: int,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""
|
||
获取资料关联的知识点列表
|
||
"""
|
||
knowledge_points = await course_service.get_material_knowledge_points(
|
||
db, material_id=material_id
|
||
)
|
||
return ResponseModel(data=knowledge_points, message="获取知识点列表成功")
|
||
|
||
|
||
@router.post(
|
||
"/materials/{material_id}/knowledge-points",
|
||
response_model=ResponseModel[List[KnowledgePointInDB]],
|
||
status_code=status.HTTP_201_CREATED,
|
||
)
|
||
async def add_material_knowledge_points(
|
||
material_id: int,
|
||
knowledge_point_ids: List[int],
|
||
current_user: User = Depends(require_admin_or_manager),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""
|
||
为资料添加知识点关联(需要管理员或经理权限)
|
||
"""
|
||
knowledge_points = await course_service.add_material_knowledge_points(
|
||
db, material_id=material_id, knowledge_point_ids=knowledge_point_ids
|
||
)
|
||
return ResponseModel(data=knowledge_points, message="添加知识点成功")
|
||
|
||
|
||
@router.delete(
|
||
"/materials/{material_id}/knowledge-points/{knowledge_point_id}",
|
||
response_model=ResponseModel[bool],
|
||
)
|
||
async def remove_material_knowledge_point(
|
||
material_id: int,
|
||
knowledge_point_id: int,
|
||
current_user: User = Depends(require_admin_or_manager),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""
|
||
移除资料的知识点关联(需要管理员或经理权限)
|
||
"""
|
||
success = await course_service.remove_material_knowledge_point(
|
||
db, material_id=material_id, knowledge_point_id=knowledge_point_id
|
||
)
|
||
return ResponseModel(data=success, message="移除知识点成功" if success else "移除失败")
|
||
|
||
|
||
# 成长路径相关API
|
||
@router.post(
|
||
"/growth-paths",
|
||
response_model=ResponseModel[GrowthPathInDB],
|
||
status_code=status.HTTP_201_CREATED,
|
||
)
|
||
async def create_growth_path(
|
||
path_in: GrowthPathCreate,
|
||
current_user: User = Depends(require_admin),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""
|
||
创建成长路径(需要管理员权限)
|
||
|
||
- **name**: 路径名称
|
||
- **description**: 路径描述
|
||
- **target_role**: 目标角色
|
||
- **courses**: 课程列表(包含course_id、order、is_required)
|
||
- **estimated_duration_days**: 预计完成天数
|
||
- **is_active**: 是否启用
|
||
"""
|
||
growth_path = await growth_path_service.create_growth_path(
|
||
db, path_in=path_in, created_by=current_user.id
|
||
)
|
||
|
||
return ResponseModel(data=growth_path, message="创建成长路径成功")
|
||
|
||
|
||
@router.get(
|
||
"/growth-paths", response_model=ResponseModel[PaginatedResponse[GrowthPathInDB]]
|
||
)
|
||
async def get_growth_paths(
|
||
page: int = Query(1, ge=1, description="页码"),
|
||
size: int = Query(20, ge=1, le=100, description="每页数量"),
|
||
is_active: Optional[bool] = Query(None, description="是否启用"),
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""
|
||
获取成长路径列表
|
||
|
||
- **page**: 页码
|
||
- **size**: 每页数量
|
||
- **is_active**: 是否启用筛选
|
||
"""
|
||
page_params = PaginationParams(page=page, page_size=size)
|
||
|
||
filters = []
|
||
if is_active is not None:
|
||
from app.models.course import GrowthPath
|
||
|
||
filters.append(GrowthPath.is_active == is_active)
|
||
|
||
result = await growth_path_service.get_page(
|
||
db, page_params=page_params, filters=filters
|
||
)
|
||
|
||
return ResponseModel(data=result, message="获取成长路径列表成功")
|
||
|
||
|
||
# 课程考试设置相关API
|
||
@router.get(
|
||
"/{course_id}/exam-settings",
|
||
response_model=ResponseModel[Optional[CourseExamSettingsInDB]],
|
||
)
|
||
async def get_course_exam_settings(
|
||
course_id: int,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""
|
||
获取课程的考试设置
|
||
|
||
- **course_id**: 课程ID
|
||
"""
|
||
# 检查课程是否存在
|
||
course = await course_service.get_by_id(db, course_id)
|
||
if not course:
|
||
raise NotFoundError(f"课程ID {course_id} 不存在")
|
||
|
||
# 获取考试设置
|
||
from app.services.course_exam_service import course_exam_service
|
||
settings = await course_exam_service.get_by_course_id(db, course_id)
|
||
|
||
# 添加调试日志
|
||
if settings:
|
||
logger.info(
|
||
f"📊 获取考试设置成功 - course_id: {course_id}, "
|
||
f"单选: {settings.single_choice_count}, 多选: {settings.multiple_choice_count}, "
|
||
f"判断: {settings.true_false_count}, 填空: {settings.fill_blank_count}, "
|
||
f"问答: {settings.essay_count}, 难度: {settings.difficulty_level}"
|
||
)
|
||
else:
|
||
logger.warning(f"⚠️ 课程 {course_id} 没有配置考试设置,将使用默认值")
|
||
|
||
return ResponseModel(data=settings, message="获取考试设置成功")
|
||
|
||
|
||
@router.post(
|
||
"/{course_id}/exam-settings",
|
||
response_model=ResponseModel[CourseExamSettingsInDB],
|
||
status_code=status.HTTP_201_CREATED,
|
||
)
|
||
async def create_course_exam_settings(
|
||
course_id: int,
|
||
settings_in: CourseExamSettingsCreate,
|
||
current_user: User = Depends(require_admin_or_manager),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""
|
||
创建或更新课程的考试设置(需要管理员权限)
|
||
|
||
- **course_id**: 课程ID
|
||
- **settings_in**: 考试设置数据
|
||
"""
|
||
# 检查课程是否存在
|
||
course = await course_service.get_by_id(db, course_id)
|
||
if not course:
|
||
raise NotFoundError(f"课程ID {course_id} 不存在")
|
||
|
||
# 创建或更新考试设置
|
||
from app.services.course_exam_service import course_exam_service
|
||
settings = await course_exam_service.create_or_update(
|
||
db, course_id=course_id, settings_in=settings_in, user_id=current_user.id
|
||
)
|
||
|
||
return ResponseModel(data=settings, message="保存考试设置成功")
|
||
|
||
|
||
@router.put(
|
||
"/{course_id}/exam-settings",
|
||
response_model=ResponseModel[CourseExamSettingsInDB],
|
||
)
|
||
async def update_course_exam_settings(
|
||
course_id: int,
|
||
settings_in: CourseExamSettingsUpdate,
|
||
current_user: User = Depends(require_admin_or_manager),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""
|
||
更新课程的考试设置(需要管理员权限)
|
||
|
||
- **course_id**: 课程ID
|
||
- **settings_in**: 更新的考试设置数据
|
||
"""
|
||
# 检查课程是否存在
|
||
course = await course_service.get_by_id(db, course_id)
|
||
if not course:
|
||
raise NotFoundError(f"课程ID {course_id} 不存在")
|
||
|
||
# 更新考试设置
|
||
from app.services.course_exam_service import course_exam_service
|
||
settings = await course_exam_service.update(
|
||
db, course_id=course_id, settings_in=settings_in, user_id=current_user.id
|
||
)
|
||
|
||
return ResponseModel(data=settings, message="更新考试设置成功")
|
||
|
||
|
||
# 课程岗位分配相关API
|
||
@router.get(
|
||
"/{course_id}/positions",
|
||
response_model=ResponseModel[List[CoursePositionAssignmentInDB]],
|
||
)
|
||
async def get_course_positions(
|
||
course_id: int,
|
||
course_type: Optional[str] = Query(None, pattern="^(required|optional)$", description="课程类型筛选"),
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""
|
||
获取课程的岗位分配列表
|
||
|
||
- **course_id**: 课程ID
|
||
- **course_type**: 课程类型筛选(required必修/optional选修)
|
||
"""
|
||
# 检查课程是否存在
|
||
course = await course_service.get_by_id(db, course_id)
|
||
if not course:
|
||
raise NotFoundError(f"课程ID {course_id} 不存在")
|
||
|
||
# 获取岗位分配列表
|
||
from app.services.course_position_service import course_position_service
|
||
assignments = await course_position_service.get_course_positions(
|
||
db, course_id=course_id, course_type=course_type
|
||
)
|
||
|
||
return ResponseModel(data=assignments, message="获取岗位分配列表成功")
|
||
|
||
|
||
@router.post(
|
||
"/{course_id}/positions",
|
||
response_model=ResponseModel[List[CoursePositionAssignmentInDB]],
|
||
status_code=status.HTTP_201_CREATED,
|
||
)
|
||
async def assign_course_positions(
|
||
course_id: int,
|
||
assignments: List[CoursePositionAssignment],
|
||
current_user: User = Depends(require_admin),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""
|
||
批量分配课程到岗位(需要管理员权限)
|
||
|
||
- **course_id**: 课程ID
|
||
- **assignments**: 岗位分配列表
|
||
"""
|
||
# 检查课程是否存在
|
||
course = await course_service.get_by_id(db, course_id)
|
||
if not course:
|
||
raise NotFoundError(f"课程ID {course_id} 不存在")
|
||
|
||
# 批量分配岗位
|
||
from app.services.course_position_service import course_position_service
|
||
result = await course_position_service.batch_assign_positions(
|
||
db, course_id=course_id, assignments=assignments, user_id=current_user.id
|
||
)
|
||
|
||
# 发送课程分配通知给相关岗位的学员
|
||
try:
|
||
from app.models.position_member import PositionMember
|
||
from app.services.notification_service import notification_service
|
||
from app.schemas.notification import NotificationBatchCreate, NotificationType
|
||
|
||
# 获取所有分配岗位的学员ID
|
||
position_ids = [a.position_id for a in assignments]
|
||
if position_ids:
|
||
member_result = await db.execute(
|
||
select(PositionMember.user_id).where(
|
||
PositionMember.position_id.in_(position_ids),
|
||
PositionMember.is_deleted == False
|
||
).distinct()
|
||
)
|
||
user_ids = [row[0] for row in member_result.fetchall()]
|
||
|
||
if user_ids:
|
||
notification_batch = NotificationBatchCreate(
|
||
user_ids=user_ids,
|
||
title="新课程通知",
|
||
content=f"您所在岗位有新课程「{course.name}」已分配,请及时学习。",
|
||
type=NotificationType.COURSE_ASSIGN,
|
||
related_id=course_id,
|
||
related_type="course",
|
||
sender_id=current_user.id
|
||
)
|
||
|
||
await notification_service.batch_create_notifications(
|
||
db=db,
|
||
batch_in=notification_batch
|
||
)
|
||
except Exception as e:
|
||
# 通知发送失败不影响课程分配结果
|
||
import logging
|
||
logging.getLogger(__name__).error(f"发送课程分配通知失败: {str(e)}")
|
||
|
||
return ResponseModel(data=result, message="岗位分配成功")
|
||
|
||
|
||
@router.delete(
|
||
"/{course_id}/positions/{position_id}",
|
||
response_model=ResponseModel[bool],
|
||
)
|
||
async def remove_course_position(
|
||
course_id: int,
|
||
position_id: int,
|
||
current_user: User = Depends(require_admin),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""
|
||
移除课程的岗位分配(需要管理员权限)
|
||
|
||
- **course_id**: 课程ID
|
||
- **position_id**: 岗位ID
|
||
"""
|
||
# 检查课程是否存在
|
||
course = await course_service.get_by_id(db, course_id)
|
||
if not course:
|
||
raise NotFoundError(f"课程ID {course_id} 不存在")
|
||
|
||
# 移除岗位分配
|
||
from app.services.course_position_service import course_position_service
|
||
success = await course_position_service.remove_position_assignment(
|
||
db, course_id=course_id, position_id=position_id, user_id=current_user.id
|
||
)
|
||
|
||
return ResponseModel(data=success, message="移除岗位分配成功" if success else "移除岗位分配失败")
|
||
|
||
|
||
async def _trigger_knowledge_analysis(
|
||
db: AsyncSession,
|
||
course_id: int,
|
||
material_id: int,
|
||
file_url: str,
|
||
course_title: str,
|
||
user_id: int
|
||
):
|
||
"""
|
||
后台触发知识点分析任务
|
||
|
||
注意:此函数在后台任务中执行,异常不会影响资料添加的成功响应
|
||
"""
|
||
try:
|
||
from app.services.ai.knowledge_analysis_v2 import knowledge_analysis_service_v2
|
||
|
||
logger.info(
|
||
f"后台知识点分析开始 - course_id: {course_id}, material_id: {material_id}, file_url: {file_url}, user_id: {user_id}"
|
||
)
|
||
|
||
result = await knowledge_analysis_service_v2.analyze_course_material(
|
||
db=db,
|
||
course_id=course_id,
|
||
material_id=material_id,
|
||
file_url=file_url,
|
||
course_title=course_title,
|
||
user_id=user_id
|
||
)
|
||
|
||
logger.info(
|
||
f"后台知识点分析完成 - course_id: {course_id}, material_id: {material_id}, knowledge_points_count: {result.get('knowledge_points_count', 0)}, user_id: {user_id}"
|
||
)
|
||
|
||
except FileNotFoundError as e:
|
||
# 文件不存在时记录警告,但不记录完整堆栈
|
||
logger.warning(
|
||
f"后台知识点分析失败(文件不存在) - course_id: {course_id}, material_id: {material_id}, "
|
||
f"file_url: {file_url}, error: {str(e)}, user_id: {user_id}"
|
||
)
|
||
except Exception as e:
|
||
# 其他异常记录详细信息
|
||
logger.error(
|
||
f"后台知识点分析失败 - course_id: {course_id}, material_id: {material_id}, error: {str(e)}",
|
||
exc_info=True
|
||
)
|