feat: 初始化考培练系统项目

- 从服务器拉取完整代码
- 按框架规范整理项目结构
- 配置 Drone CI 测试环境部署
- 包含后端(FastAPI)、前端(Vue3)、管理端

技术栈: Vue3 + TypeScript + FastAPI + MySQL
This commit is contained in:
111
2026-01-24 19:33:28 +08:00
commit 998211c483
1197 changed files with 228429 additions and 0 deletions

View File

@@ -0,0 +1,270 @@
"""
课程相关数据库模型
"""
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="目标角色"
)
# 路径配置
courses: Mapped[Optional[List[dict]]] = mapped_column(
JSON, nullable=True, comment="课程列表[{course_id, order, is_required}]"
)
# 预计时长
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="排序顺序"
)
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生成"
)