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

153
backend/app/models/exam.py Normal file
View File

@@ -0,0 +1,153 @@
"""
考试相关模型定义
"""
from datetime import datetime
from typing import List, Optional
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, JSON, Float, func
from sqlalchemy.orm import relationship, Mapped, mapped_column
from app.models.base import BaseModel
class Exam(BaseModel):
"""考试记录模型"""
__tablename__ = "exams"
__allow_unmapped__ = True
id: Mapped[int] = mapped_column(Integer, primary_key=True)
user_id: Mapped[int] = mapped_column(
Integer, ForeignKey("users.id"), nullable=False, index=True
)
course_id: Mapped[int] = mapped_column(
Integer, ForeignKey("courses.id"), nullable=False, index=True
)
# 考试信息
exam_name: Mapped[str] = mapped_column(String(255), nullable=False)
question_count: Mapped[int] = mapped_column(Integer, default=10)
total_score: Mapped[float] = mapped_column(Float, default=100.0)
pass_score: Mapped[float] = mapped_column(Float, default=60.0)
# 考试时间
start_time: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), comment="开始时间(北京时间)")
end_time: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True, comment="结束时间(北京时间)")
duration_minutes: Mapped[int] = mapped_column(Integer, default=60) # 考试时长(分钟)
# 考试结果
score: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
# 三轮考试得分
round1_score: Mapped[Optional[float]] = mapped_column(Float, nullable=True, comment="第一轮得分")
round2_score: Mapped[Optional[float]] = mapped_column(Float, nullable=True, comment="第二轮得分")
round3_score: Mapped[Optional[float]] = mapped_column(Float, nullable=True, comment="第三轮得分")
is_passed: Mapped[Optional[bool]] = mapped_column(nullable=True)
# 考试状态: started, submitted, timeout
status: Mapped[str] = mapped_column(String(20), default="started", index=True)
# 考试数据JSON格式存储题目和答案
questions: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
answers: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
# 关系
user = relationship("User", back_populates="exams")
course = relationship("Course", back_populates="exams")
results = relationship("ExamResult", back_populates="exam")
def __repr__(self):
return f"<Exam(id={self.id}, user_id={self.user_id}, course_id={self.course_id}, status={self.status})>"
class Question(BaseModel):
"""题目模型"""
__tablename__ = "questions"
__allow_unmapped__ = True
id: Mapped[int] = mapped_column(Integer, primary_key=True)
course_id: Mapped[int] = mapped_column(
Integer, ForeignKey("courses.id"), nullable=False, index=True
)
# 题目类型: single_choice, multiple_choice, true_false, fill_blank, essay
question_type: Mapped[str] = mapped_column(String(20), nullable=False, index=True)
# 题目内容
title: Mapped[str] = mapped_column(Text, nullable=False)
content: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# 选项JSON格式适用于选择题
options: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
# 答案
correct_answer: Mapped[str] = mapped_column(Text, nullable=False)
explanation: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# 分值
score: Mapped[float] = mapped_column(Float, default=10.0)
# 难度等级: easy, medium, hard
difficulty: Mapped[str] = mapped_column(String(10), default="medium", index=True)
# 标签JSON格式
tags: Mapped[Optional[list]] = mapped_column(JSON, nullable=True)
# 使用统计
usage_count: Mapped[int] = mapped_column(Integer, default=0)
correct_count: Mapped[int] = mapped_column(Integer, default=0)
# 状态
is_active: Mapped[bool] = mapped_column(default=True, index=True)
# 关系
course = relationship("Course", back_populates="questions")
def __repr__(self):
return f"<Question(id={self.id}, course_id={self.course_id}, type={self.question_type})>"
class ExamResult(BaseModel):
"""考试结果详情模型"""
__tablename__ = "exam_results"
__allow_unmapped__ = True
id: Mapped[int] = mapped_column(Integer, primary_key=True)
exam_id: Mapped[int] = mapped_column(
Integer, ForeignKey("exams.id"), nullable=False, index=True
)
question_id: Mapped[int] = mapped_column(
Integer, ForeignKey("questions.id"), nullable=False
)
# 用户答案
user_answer: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# 是否正确
is_correct: Mapped[bool] = mapped_column(default=False)
# 得分
score: Mapped[float] = mapped_column(Float, default=0.0)
# 答题时长(秒)
answer_time: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
# 关系
exam = relationship("Exam", back_populates="results")
question = relationship("Question")
def __repr__(self):
return f"<ExamResult(id={self.id}, exam_id={self.exam_id}, question_id={self.question_id}, is_correct={self.is_correct})>"
# 在模型文件末尾添加关系定义
# 需要在User模型中添加
# exams = relationship("Exam", back_populates="user")
# 需要在Course模型中添加
# exams = relationship("Exam", back_populates="course")
# questions = relationship("Question", back_populates="course")
# 需要在Exam模型中添加
# results = relationship("ExamResult", back_populates="exam")