feat: 初始化考培练系统项目
- 从服务器拉取完整代码 - 按框架规范整理项目结构 - 配置 Drone CI 测试环境部署 - 包含后端(FastAPI)、前端(Vue3)、管理端 技术栈: Vue3 + TypeScript + FastAPI + MySQL
This commit is contained in:
364
backend/app/schemas/course.py
Normal file
364
backend/app/schemas/course.py
Normal file
@@ -0,0 +1,364 @@
|
||||
"""
|
||||
课程相关的数据验证模型
|
||||
"""
|
||||
from typing import Optional, List
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
|
||||
from pydantic import BaseModel, Field, ConfigDict, field_validator
|
||||
|
||||
from app.models.course import CourseStatus, CourseCategory
|
||||
|
||||
|
||||
class CourseBase(BaseModel):
|
||||
"""
|
||||
课程基础模型
|
||||
"""
|
||||
|
||||
name: str = Field(..., min_length=1, max_length=200, description="课程名称")
|
||||
description: Optional[str] = Field(None, description="课程描述")
|
||||
category: CourseCategory = Field(default=CourseCategory.GENERAL, description="课程分类")
|
||||
cover_image: Optional[str] = Field(None, max_length=500, description="封面图片URL")
|
||||
duration_hours: Optional[float] = Field(None, ge=0, description="课程时长(小时)")
|
||||
difficulty_level: Optional[int] = Field(None, ge=1, le=5, description="难度等级(1-5)")
|
||||
tags: Optional[List[str]] = Field(default_factory=list, description="标签列表")
|
||||
sort_order: int = Field(default=0, description="排序顺序")
|
||||
is_featured: bool = Field(default=False, description="是否推荐")
|
||||
allow_download: bool = Field(default=False, description="是否允许下载资料")
|
||||
|
||||
@field_validator("category", mode="before")
|
||||
@classmethod
|
||||
def normalize_category(cls, v):
|
||||
"""允许使用枚举的名称或值(忽略大小写)。空字符串使用默认值。"""
|
||||
if isinstance(v, CourseCategory):
|
||||
return v
|
||||
if isinstance(v, str):
|
||||
s = v.strip()
|
||||
# 空字符串使用默认值
|
||||
if not s:
|
||||
return CourseCategory.GENERAL
|
||||
# 优先按值匹配(technology 等)
|
||||
try:
|
||||
return CourseCategory(s.lower())
|
||||
except Exception:
|
||||
pass
|
||||
# 再按名称匹配(TECHNOLOGY 等)
|
||||
try:
|
||||
return CourseCategory[s.upper()]
|
||||
except Exception:
|
||||
pass
|
||||
return v
|
||||
|
||||
|
||||
class CourseCreate(CourseBase):
|
||||
"""
|
||||
创建课程模型
|
||||
"""
|
||||
|
||||
status: CourseStatus = Field(default=CourseStatus.DRAFT, description="课程状态")
|
||||
|
||||
|
||||
class CourseUpdate(BaseModel):
|
||||
"""
|
||||
更新课程模型
|
||||
"""
|
||||
|
||||
name: Optional[str] = Field(None, min_length=1, max_length=200, description="课程名称")
|
||||
description: Optional[str] = Field(None, description="课程描述")
|
||||
category: Optional[CourseCategory] = Field(None, description="课程分类")
|
||||
status: Optional[CourseStatus] = Field(None, description="课程状态")
|
||||
cover_image: Optional[str] = Field(None, max_length=500, description="封面图片URL")
|
||||
duration_hours: Optional[float] = Field(None, ge=0, description="课程时长(小时)")
|
||||
difficulty_level: Optional[int] = Field(None, ge=1, le=5, description="难度等级(1-5)")
|
||||
tags: Optional[List[str]] = Field(None, description="标签列表")
|
||||
sort_order: Optional[int] = Field(None, description="排序顺序")
|
||||
is_featured: Optional[bool] = Field(None, description="是否推荐")
|
||||
allow_download: Optional[bool] = Field(None, description="是否允许下载资料")
|
||||
|
||||
@field_validator("category", mode="before")
|
||||
@classmethod
|
||||
def normalize_category_update(cls, v):
|
||||
if v is None:
|
||||
return v
|
||||
if isinstance(v, CourseCategory):
|
||||
return v
|
||||
if isinstance(v, str):
|
||||
s = v.strip()
|
||||
if not s: # 空字符串视为None(不更新)
|
||||
return None
|
||||
try:
|
||||
return CourseCategory(s.lower())
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
return CourseCategory[s.upper()]
|
||||
except Exception:
|
||||
pass
|
||||
return v
|
||||
|
||||
|
||||
class CourseInDB(CourseBase):
|
||||
"""
|
||||
数据库中的课程模型
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int = Field(..., description="课程ID")
|
||||
status: CourseStatus = Field(..., description="课程状态")
|
||||
created_at: datetime = Field(..., description="创建时间")
|
||||
updated_at: datetime = Field(..., description="更新时间")
|
||||
published_at: Optional[datetime] = Field(None, description="发布时间")
|
||||
publisher_id: Optional[int] = Field(None, description="发布人ID")
|
||||
created_by: Optional[int] = Field(None, description="创建人ID")
|
||||
updated_by: Optional[int] = Field(None, description="更新人ID")
|
||||
# 用户岗位相关的课程类型(必修/选修),非数据库字段,由API动态计算
|
||||
course_type: Optional[str] = Field(None, description="课程类型:required=必修, optional=选修")
|
||||
|
||||
|
||||
class CourseList(BaseModel):
|
||||
"""
|
||||
课程列表查询参数
|
||||
"""
|
||||
|
||||
status: Optional[CourseStatus] = Field(None, description="课程状态")
|
||||
category: Optional[CourseCategory] = Field(None, description="课程分类")
|
||||
is_featured: Optional[bool] = Field(None, description="是否推荐")
|
||||
keyword: Optional[str] = Field(None, description="搜索关键词")
|
||||
|
||||
|
||||
# 课程资料相关模型
|
||||
class CourseMaterialBase(BaseModel):
|
||||
"""
|
||||
课程资料基础模型
|
||||
"""
|
||||
|
||||
name: str = Field(..., min_length=1, max_length=200, description="资料名称")
|
||||
description: Optional[str] = Field(None, description="资料描述")
|
||||
sort_order: int = Field(default=0, description="排序顺序")
|
||||
|
||||
|
||||
class CourseMaterialCreate(CourseMaterialBase):
|
||||
"""
|
||||
创建课程资料模型
|
||||
"""
|
||||
|
||||
file_url: str = Field(..., max_length=500, description="文件URL")
|
||||
file_type: str = Field(..., max_length=50, description="文件类型")
|
||||
file_size: int = Field(..., gt=0, description="文件大小(字节)")
|
||||
|
||||
@field_validator("file_type")
|
||||
def validate_file_type(cls, v):
|
||||
"""验证文件类型
|
||||
支持格式:TXT、Markdown、MDX、PDF、HTML、Excel、Word、CSV、VTT、Properties
|
||||
"""
|
||||
allowed_types = [
|
||||
"txt", "md", "mdx", "pdf", "html", "htm",
|
||||
"xlsx", "xls", "docx", "doc", "csv", "vtt", "properties"
|
||||
]
|
||||
file_ext = v.lower()
|
||||
if file_ext not in allowed_types:
|
||||
raise ValueError(f"不支持的文件类型: {v}。允许的类型: TXT、Markdown、MDX、PDF、HTML、Excel、Word、CSV、VTT、Properties")
|
||||
return file_ext
|
||||
|
||||
|
||||
class CourseMaterialInDB(CourseMaterialBase):
|
||||
"""
|
||||
数据库中的课程资料模型
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int = Field(..., description="资料ID")
|
||||
course_id: int = Field(..., description="课程ID")
|
||||
file_url: str = Field(..., description="文件URL")
|
||||
file_type: str = Field(..., description="文件类型")
|
||||
file_size: int = Field(..., description="文件大小(字节)")
|
||||
created_at: datetime = Field(..., description="创建时间")
|
||||
updated_at: datetime = Field(..., description="更新时间")
|
||||
|
||||
|
||||
# 知识点相关模型
|
||||
class KnowledgePointBase(BaseModel):
|
||||
"""
|
||||
知识点基础模型
|
||||
"""
|
||||
|
||||
name: str = Field(..., min_length=1, max_length=200, description="知识点名称")
|
||||
description: Optional[str] = Field(None, description="知识点描述")
|
||||
type: str = Field(default="理论知识", description="知识点类型")
|
||||
source: int = Field(default=0, description="来源:0=手动,1=AI分析")
|
||||
topic_relation: Optional[str] = Field(None, description="与主题的关系描述")
|
||||
|
||||
|
||||
class KnowledgePointCreate(KnowledgePointBase):
|
||||
"""
|
||||
创建知识点模型
|
||||
"""
|
||||
|
||||
material_id: int = Field(..., description="关联资料ID(必填)")
|
||||
|
||||
|
||||
class KnowledgePointUpdate(BaseModel):
|
||||
"""
|
||||
更新知识点模型
|
||||
"""
|
||||
|
||||
name: Optional[str] = Field(None, min_length=1, max_length=200, description="知识点名称")
|
||||
description: Optional[str] = Field(None, description="知识点描述")
|
||||
type: Optional[str] = Field(None, description="知识点类型")
|
||||
source: Optional[int] = Field(None, description="来源:0=手动,1=AI分析")
|
||||
topic_relation: Optional[str] = Field(None, description="与主题的关系描述")
|
||||
material_id: int = Field(..., description="关联资料ID(必填)")
|
||||
|
||||
|
||||
class KnowledgePointInDB(KnowledgePointBase):
|
||||
"""
|
||||
数据库中的知识点模型
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int = Field(..., description="知识点ID")
|
||||
course_id: int = Field(..., description="课程ID")
|
||||
material_id: int = Field(..., description="关联资料ID")
|
||||
created_at: datetime = Field(..., description="创建时间")
|
||||
updated_at: datetime = Field(..., description="更新时间")
|
||||
|
||||
|
||||
class KnowledgePointTree(KnowledgePointInDB):
|
||||
"""
|
||||
知识点树形结构
|
||||
"""
|
||||
|
||||
children: List["KnowledgePointTree"] = Field(
|
||||
default_factory=list, description="子知识点"
|
||||
)
|
||||
|
||||
|
||||
# 成长路径相关模型
|
||||
class GrowthPathCourse(BaseModel):
|
||||
"""
|
||||
成长路径中的课程
|
||||
"""
|
||||
|
||||
course_id: int = Field(..., description="课程ID")
|
||||
order: int = Field(..., ge=0, description="排序")
|
||||
is_required: bool = Field(default=True, description="是否必修")
|
||||
|
||||
|
||||
class GrowthPathBase(BaseModel):
|
||||
"""
|
||||
成长路径基础模型
|
||||
"""
|
||||
|
||||
name: str = Field(..., min_length=1, max_length=200, description="路径名称")
|
||||
description: Optional[str] = Field(None, description="路径描述")
|
||||
target_role: Optional[str] = Field(None, max_length=100, description="目标角色")
|
||||
courses: List[GrowthPathCourse] = Field(default_factory=list, description="课程列表")
|
||||
estimated_duration_days: Optional[int] = Field(None, ge=1, description="预计完成天数")
|
||||
is_active: bool = Field(default=True, description="是否启用")
|
||||
sort_order: int = Field(default=0, description="排序顺序")
|
||||
|
||||
|
||||
class GrowthPathCreate(GrowthPathBase):
|
||||
"""
|
||||
创建成长路径模型
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class GrowthPathInDB(GrowthPathBase):
|
||||
"""
|
||||
数据库中的成长路径模型
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int = Field(..., description="路径ID")
|
||||
created_at: datetime = Field(..., description="创建时间")
|
||||
updated_at: datetime = Field(..., description="更新时间")
|
||||
|
||||
|
||||
# 课程考试设置相关Schema
|
||||
class CourseExamSettingsBase(BaseModel):
|
||||
"""
|
||||
课程考试设置基础模型
|
||||
"""
|
||||
single_choice_count: int = Field(default=4, ge=0, le=50, description="单选题数量")
|
||||
multiple_choice_count: int = Field(default=2, ge=0, le=30, description="多选题数量")
|
||||
true_false_count: int = Field(default=1, ge=0, le=20, description="判断题数量")
|
||||
fill_blank_count: int = Field(default=2, ge=0, le=10, description="填空题数量")
|
||||
essay_count: int = Field(default=1, ge=0, le=10, description="问答题数量")
|
||||
|
||||
duration_minutes: int = Field(default=10, ge=10, le=180, description="考试时长(分钟)")
|
||||
difficulty_level: int = Field(default=3, ge=1, le=5, description="难度系数(1-5)")
|
||||
passing_score: int = Field(default=60, ge=0, le=100, description="及格分数")
|
||||
|
||||
is_enabled: bool = Field(default=True, description="是否启用")
|
||||
show_answer_immediately: bool = Field(default=False, description="是否立即显示答案")
|
||||
allow_retake: bool = Field(default=True, description="是否允许重考")
|
||||
max_retake_times: Optional[int] = Field(None, ge=1, le=10, description="最大重考次数")
|
||||
|
||||
|
||||
class CourseExamSettingsCreate(CourseExamSettingsBase):
|
||||
"""
|
||||
创建课程考试设置模型
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class CourseExamSettingsUpdate(BaseModel):
|
||||
"""
|
||||
更新课程考试设置模型
|
||||
"""
|
||||
single_choice_count: Optional[int] = Field(None, ge=0, le=50, description="单选题数量")
|
||||
multiple_choice_count: Optional[int] = Field(None, ge=0, le=30, description="多选题数量")
|
||||
true_false_count: Optional[int] = Field(None, ge=0, le=20, description="判断题数量")
|
||||
fill_blank_count: Optional[int] = Field(None, ge=0, le=10, description="填空题数量")
|
||||
essay_count: Optional[int] = Field(None, ge=0, le=10, description="问答题数量")
|
||||
|
||||
duration_minutes: Optional[int] = Field(None, ge=10, le=180, description="考试时长(分钟)")
|
||||
difficulty_level: Optional[int] = Field(None, ge=1, le=5, description="难度系数(1-5)")
|
||||
passing_score: Optional[int] = Field(None, ge=0, le=100, description="及格分数")
|
||||
|
||||
is_enabled: Optional[bool] = Field(None, description="是否启用")
|
||||
show_answer_immediately: Optional[bool] = Field(None, description="是否立即显示答案")
|
||||
allow_retake: Optional[bool] = Field(None, description="是否允许重考")
|
||||
max_retake_times: Optional[int] = Field(None, ge=1, le=10, description="最大重考次数")
|
||||
|
||||
|
||||
class CourseExamSettingsInDB(CourseExamSettingsBase):
|
||||
"""
|
||||
数据库中的课程考试设置模型
|
||||
"""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int = Field(..., description="设置ID")
|
||||
course_id: int = Field(..., description="课程ID")
|
||||
created_at: datetime = Field(..., description="创建时间")
|
||||
updated_at: datetime = Field(..., description="更新时间")
|
||||
|
||||
|
||||
# 岗位分配相关Schema
|
||||
class CoursePositionAssignment(BaseModel):
|
||||
"""
|
||||
课程岗位分配模型
|
||||
"""
|
||||
position_id: int = Field(..., description="岗位ID")
|
||||
course_type: str = Field(default="required", pattern="^(required|optional)$", description="课程类型:required必修/optional选修")
|
||||
priority: int = Field(default=0, description="优先级/排序")
|
||||
|
||||
|
||||
class CoursePositionAssignmentInDB(CoursePositionAssignment):
|
||||
"""
|
||||
数据库中的课程岗位分配模型
|
||||
"""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int = Field(..., description="分配ID")
|
||||
course_id: int = Field(..., description="课程ID")
|
||||
position_name: Optional[str] = Field(None, description="岗位名称")
|
||||
position_description: Optional[str] = Field(None, description="岗位描述")
|
||||
member_count: Optional[int] = Field(None, description="岗位成员数")
|
||||
Reference in New Issue
Block a user