Files
012-kaopeilian/backend/app/models/course.py
yuliang_guo b4906c543b
All checks were successful
continuous-integration/drone/push Build is passing
feat: 实现成长路径功能
- 新增数据库表: growth_path_nodes, user_growth_path_progress, user_node_completions
- 新增 Model: GrowthPathNode, UserGrowthPathProgress, UserNodeCompletion
- 新增 Service: GrowthPathService(管理端CRUD、学员端进度追踪)
- 新增 API: 学员端获取成长路径、管理端CRUD
- 前端学员端从API动态加载成长路径数据
- 更新管理端API接口定义
2026-01-30 15:37:14 +08:00

286 lines
8.8 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 enum import Enum
from typing import List, Optional
from datetime import datetime
from sqlalchemy import (
String,
Text,
Integer,
Boolean,
ForeignKey,
Enum as SQLEnum,
Float,
JSON,
DateTime,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.models.base import BaseModel, SoftDeleteMixin, AuditMixin
class CourseStatus(str, Enum):
"""课程状态枚举"""
DRAFT = "draft" # 草稿
PUBLISHED = "published" # 已发布
ARCHIVED = "archived" # 已归档
class CourseCategory(str, Enum):
"""课程分类枚举"""
TECHNOLOGY = "technology" # 技术
MANAGEMENT = "management" # 管理
BUSINESS = "business" # 业务
GENERAL = "general" # 通用
class Course(BaseModel, SoftDeleteMixin, AuditMixin):
"""
课程表
"""
__tablename__ = "courses"
# 基本信息
name: Mapped[str] = mapped_column(String(200), nullable=False, comment="课程名称")
description: Mapped[Optional[str]] = mapped_column(
Text, nullable=True, comment="课程描述"
)
category: Mapped[CourseCategory] = mapped_column(
SQLEnum(
CourseCategory,
values_callable=lambda enum_cls: [e.value for e in enum_cls],
validate_strings=True,
),
default=CourseCategory.GENERAL,
nullable=False,
comment="课程分类",
)
status: Mapped[CourseStatus] = mapped_column(
SQLEnum(
CourseStatus,
values_callable=lambda enum_cls: [e.value for e in enum_cls],
validate_strings=True,
),
default=CourseStatus.DRAFT,
nullable=False,
comment="课程状态",
)
# 课程详情
cover_image: Mapped[Optional[str]] = mapped_column(
String(500), nullable=True, comment="封面图片URL"
)
duration_hours: Mapped[Optional[float]] = mapped_column(
Float, nullable=True, comment="课程时长(小时)"
)
difficulty_level: Mapped[Optional[int]] = mapped_column(
Integer, nullable=True, comment="难度等级(1-5)"
)
tags: Mapped[Optional[List[str]]] = mapped_column(
JSON, nullable=True, comment="标签列表"
)
# 发布信息
published_at: Mapped[Optional[datetime]] = mapped_column(
DateTime(timezone=True), nullable=True, comment="发布时间"
)
publisher_id: Mapped[Optional[int]] = mapped_column(
Integer, nullable=True, comment="发布人ID"
)
# 播课信息
# 播课功能Coze工作流直接写数据库
broadcast_audio_url: Mapped[Optional[str]] = mapped_column(
String(500), nullable=True, comment="播课音频URL"
)
broadcast_generated_at: Mapped[Optional[datetime]] = mapped_column(
DateTime(timezone=True), nullable=True, comment="播课生成时间"
)
# 排序和权重
sort_order: Mapped[int] = mapped_column(
Integer, default=0, nullable=False, comment="排序顺序"
)
is_featured: Mapped[bool] = mapped_column(
Boolean, default=False, nullable=False, comment="是否推荐"
)
# 统计信息
student_count: Mapped[int] = mapped_column(
Integer, default=0, nullable=False, comment="学习人数"
)
is_new: Mapped[bool] = mapped_column(
Boolean, default=True, nullable=False, comment="是否新课程"
)
# 资料下载设置
allow_download: Mapped[bool] = mapped_column(
Boolean, default=False, nullable=False, comment="是否允许下载资料"
)
# 关联关系
materials: Mapped[List["CourseMaterial"]] = relationship(
"CourseMaterial", back_populates="course"
)
knowledge_points: Mapped[List["KnowledgePoint"]] = relationship(
"KnowledgePoint", back_populates="course"
)
# 岗位分配关系(通过关联表)
position_assignments = relationship("PositionCourse", back_populates="course", cascade="all, delete-orphan")
exams = relationship("Exam", back_populates="course")
questions = relationship("Question", back_populates="course")
class CourseMaterial(BaseModel, SoftDeleteMixin, AuditMixin):
"""
课程资料表
"""
__tablename__ = "course_materials"
course_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("courses.id", ondelete="CASCADE"),
nullable=False,
comment="课程ID",
)
name: Mapped[str] = mapped_column(String(200), nullable=False, comment="资料名称")
description: Mapped[Optional[str]] = mapped_column(
Text, nullable=True, comment="资料描述"
)
file_url: Mapped[str] = mapped_column(String(500), nullable=False, comment="文件URL")
file_type: Mapped[str] = mapped_column(String(50), nullable=False, comment="文件类型")
file_size: Mapped[int] = mapped_column(Integer, nullable=False, comment="文件大小(字节)")
# 排序
sort_order: Mapped[int] = mapped_column(
Integer, default=0, nullable=False, comment="排序顺序"
)
# 关联关系
course: Mapped["Course"] = relationship("Course", back_populates="materials")
# 关联的知识点(直接关联)
knowledge_points: Mapped[List["KnowledgePoint"]] = relationship(
"KnowledgePoint", back_populates="material"
)
class KnowledgePoint(BaseModel, SoftDeleteMixin, AuditMixin):
"""
知识点表
"""
__tablename__ = "knowledge_points"
course_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("courses.id", ondelete="CASCADE"),
nullable=False,
comment="课程ID",
)
material_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("course_materials.id", ondelete="CASCADE"),
nullable=False,
comment="关联资料ID",
)
name: Mapped[str] = mapped_column(String(200), nullable=False, comment="知识点名称")
description: Mapped[Optional[str]] = mapped_column(
Text, nullable=True, comment="知识点描述"
)
type: Mapped[str] = mapped_column(
String(50), default="概念定义", nullable=False, comment="知识点类型"
)
source: Mapped[int] = mapped_column(
Integer, default=0, nullable=False, comment="来源0=手动1=AI分析"
)
topic_relation: Mapped[Optional[str]] = mapped_column(
String(200), nullable=True, comment="与主题的关系描述"
)
# 关联关系
course: Mapped["Course"] = relationship("Course", back_populates="knowledge_points")
material: Mapped["CourseMaterial"] = relationship("CourseMaterial")
class GrowthPath(BaseModel, SoftDeleteMixin):
"""
成长路径表
"""
__tablename__ = "growth_paths"
name: Mapped[str] = mapped_column(String(200), nullable=False, comment="路径名称")
description: Mapped[Optional[str]] = mapped_column(
Text, nullable=True, comment="路径描述"
)
target_role: Mapped[Optional[str]] = mapped_column(
String(100), nullable=True, comment="目标角色"
)
# 岗位关联
position_id: Mapped[Optional[int]] = mapped_column(
Integer, nullable=True, comment="关联岗位ID"
)
# 路径配置(保留用于兼容,新版使用 nodes 关联表)
courses: Mapped[Optional[List[dict]]] = mapped_column(
JSON, nullable=True, comment="课程列表[{course_id, order, is_required}]已废弃使用nodes"
)
# 阶段配置
stages: Mapped[Optional[List[dict]]] = mapped_column(
JSON, nullable=True, comment="阶段配置[{name, description, order}]"
)
# 预计时长
estimated_duration_days: Mapped[Optional[int]] = mapped_column(
Integer, nullable=True, comment="预计完成天数"
)
# 状态
is_active: Mapped[bool] = mapped_column(
Boolean, default=True, nullable=False, comment="是否启用"
)
sort_order: Mapped[int] = mapped_column(
Integer, default=0, nullable=False, comment="排序顺序"
)
# 关联关系
nodes: Mapped[List["GrowthPathNode"]] = relationship(
"GrowthPathNode", back_populates="growth_path", cascade="all, delete-orphan"
)
class MaterialKnowledgePoint(BaseModel, SoftDeleteMixin):
"""
资料知识点关联表
"""
__tablename__ = "material_knowledge_points"
material_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("course_materials.id", ondelete="CASCADE"),
nullable=False,
comment="资料ID",
)
knowledge_point_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("knowledge_points.id", ondelete="CASCADE"),
nullable=False,
comment="知识点ID",
)
sort_order: Mapped[int] = mapped_column(
Integer, default=0, nullable=False, comment="排序顺序"
)
is_ai_generated: Mapped[bool] = mapped_column(
Boolean, default=False, nullable=False, comment="是否AI生成"
)