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

316
backend/app/schemas/exam.py Normal file
View File

@@ -0,0 +1,316 @@
"""
考试相关的Schema定义
"""
from typing import List, Optional, Dict, Any
from datetime import datetime
from pydantic import BaseModel, Field
class StartExamRequest(BaseModel):
"""开始考试请求"""
course_id: int = Field(..., description="课程ID")
count: int = Field(10, ge=1, le=100, description="题目数量")
class StartExamResponse(BaseModel):
"""开始考试响应"""
exam_id: int = Field(..., description="考试ID")
class ExamAnswer(BaseModel):
"""考试答案"""
question_id: str = Field(..., description="题目ID")
answer: str = Field(..., description="答案")
class SubmitExamRequest(BaseModel):
"""提交考试请求"""
exam_id: int = Field(..., description="考试ID")
answers: List[ExamAnswer] = Field(..., description="答案列表")
class SubmitExamResponse(BaseModel):
"""提交考试响应"""
exam_id: int = Field(..., description="考试ID")
total_score: float = Field(..., description="总分")
pass_score: float = Field(..., description="及格分")
is_passed: bool = Field(..., description="是否通过")
correct_count: int = Field(..., description="正确题数")
total_count: int = Field(..., description="总题数")
accuracy: float = Field(..., description="正确率")
class QuestionInfo(BaseModel):
"""题目信息"""
id: str = Field(..., description="题目ID")
type: str = Field(..., description="题目类型")
title: str = Field(..., description="题目标题")
content: Optional[str] = Field(None, description="题目内容")
options: Optional[Dict[str, Any]] = Field(None, description="选项")
score: float = Field(..., description="分值")
class ExamResultInfo(BaseModel):
"""答题结果信息"""
question_id: int = Field(..., description="题目ID")
user_answer: Optional[str] = Field(None, description="用户答案")
is_correct: bool = Field(..., description="是否正确")
score: float = Field(..., description="得分")
class ExamDetailResponse(BaseModel):
"""考试详情响应"""
id: int = Field(..., description="考试ID")
course_id: int = Field(..., description="课程ID")
exam_name: str = Field(..., description="考试名称")
question_count: int = Field(..., description="题目数量")
total_score: float = Field(..., description="总分")
pass_score: float = Field(..., description="及格分")
start_time: Optional[str] = Field(None, description="开始时间")
end_time: Optional[str] = Field(None, description="结束时间")
duration_minutes: int = Field(..., description="考试时长(分钟)")
status: str = Field(..., description="考试状态")
score: Optional[float] = Field(None, description="得分")
is_passed: Optional[bool] = Field(None, description="是否通过")
questions: Optional[Dict[str, Any]] = Field(None, description="题目数据")
results: Optional[List[ExamResultInfo]] = Field(None, description="答题结果")
answers: Optional[Dict[str, Any]] = Field(None, description="用户答案")
class ExamRecordInfo(BaseModel):
"""考试记录信息"""
id: int = Field(..., description="考试ID")
course_id: int = Field(..., description="课程ID")
exam_name: str = Field(..., description="考试名称")
question_count: int = Field(..., description="题目数量")
total_score: float = Field(..., description="总分")
score: Optional[float] = Field(None, description="得分")
is_passed: Optional[bool] = Field(None, description="是否通过")
status: str = Field(..., description="考试状态")
start_time: Optional[str] = Field(None, description="开始时间")
end_time: Optional[str] = Field(None, description="结束时间")
created_at: str = Field(..., description="创建时间")
# 新增统计字段
accuracy: Optional[float] = Field(None, description="正确率(%)")
correct_count: Optional[int] = Field(None, description="正确题数")
wrong_count: Optional[int] = Field(None, description="错题数")
duration_seconds: Optional[int] = Field(None, description="考试用时(秒)")
course_name: Optional[str] = Field(None, description="课程名称")
question_type_stats: Optional[List[Dict[str, Any]]] = Field(None, description="分题型统计")
class ExamRecordResponse(BaseModel):
"""考试记录列表响应"""
items: List[ExamRecordInfo] = Field(..., description="考试记录列表")
total: int = Field(..., description="总数")
page: int = Field(..., description="当前页")
size: int = Field(..., description="每页数量")
pages: int = Field(..., description="总页数")
# ==================== AI服务响应Schema ====================
class MistakeRecord(BaseModel):
"""错题记录详情"""
question_id: Optional[int] = Field(None, description="题目ID")
knowledge_point_id: Optional[int] = Field(None, description="知识点ID")
question_content: str = Field(..., description="题目内容")
correct_answer: str = Field(..., description="正确答案")
user_answer: str = Field(..., description="用户答案")
class GenerateExamRequest(BaseModel):
"""生成考试试题请求"""
course_id: int = Field(..., description="课程ID")
position_id: Optional[int] = Field(None, description="岗位ID,如果不提供则从用户信息中自动获取")
current_round: int = Field(1, ge=1, le=3, description="当前轮次(1/2/3)")
exam_id: Optional[int] = Field(None, description="已存在的exam_id(第2、3轮传入)")
mistake_records: Optional[str] = Field(None, description="错题记录JSON字符串,第一轮不传此参数,第二三轮传入上一轮错题的JSON字符串")
single_choice_count: int = Field(4, ge=0, le=50, description="单选题数量")
multiple_choice_count: int = Field(2, ge=0, le=30, description="多选题数量")
true_false_count: int = Field(1, ge=0, le=20, description="判断题数量")
fill_blank_count: int = Field(2, ge=0, le=10, description="填空题数量")
essay_count: int = Field(1, ge=0, le=10, description="问答题数量")
difficulty_level: int = Field(3, ge=1, le=5, description="难度系数(1-5)")
class GenerateExamResponse(BaseModel):
"""生成考试试题响应"""
result: str = Field(..., description="试题JSON数组(字符串格式)")
workflow_run_id: Optional[str] = Field(None, description="AI服务调用ID")
task_id: Optional[str] = Field(None, description="任务ID")
exam_id: int = Field(..., description="考试ID真实的数据库ID")
class JudgeAnswerRequest(BaseModel):
"""判断主观题答案请求"""
question: str = Field(..., description="题目内容")
correct_answer: str = Field(..., description="标准答案")
user_answer: str = Field(..., description="用户提交的答案")
analysis: str = Field(..., description="正确答案的解析(来源于试题生成器)")
class JudgeAnswerResponse(BaseModel):
"""判断主观题答案响应"""
is_correct: bool = Field(..., description="是否正确")
correct_answer: str = Field(..., description="标准答案")
feedback: Optional[str] = Field(None, description="判断反馈信息")
class RecordMistakeRequest(BaseModel):
"""记录错题请求"""
exam_id: int = Field(..., description="考试ID")
question_id: Optional[int] = Field(None, description="题目ID(AI生成的题目可能为空)")
knowledge_point_id: Optional[int] = Field(None, description="知识点ID")
question_content: str = Field(..., description="题目内容")
correct_answer: str = Field(..., description="正确答案")
user_answer: str = Field(..., description="用户答案")
question_type: Optional[str] = Field(None, description="题型(single/multiple/judge/blank/essay)")
class RecordMistakeResponse(BaseModel):
"""记录错题响应"""
id: int = Field(..., description="错题记录ID")
created_at: datetime = Field(..., description="创建时间")
class MistakeRecordItem(BaseModel):
"""错题记录项"""
id: int = Field(..., description="错题记录ID")
question_id: Optional[int] = Field(None, description="题目ID")
knowledge_point_id: Optional[int] = Field(None, description="知识点ID")
question_content: str = Field(..., description="题目内容")
correct_answer: str = Field(..., description="正确答案")
user_answer: str = Field(..., description="用户答案")
created_at: datetime = Field(..., description="创建时间")
class GetMistakesResponse(BaseModel):
"""获取错题记录响应"""
mistakes: List[MistakeRecordItem] = Field(..., description="错题列表")
# ==================== 成绩报告和错题本相关Schema ====================
class RoundScores(BaseModel):
"""三轮得分"""
round1: Optional[float] = Field(None, description="第一轮得分")
round2: Optional[float] = Field(None, description="第二轮得分")
round3: Optional[float] = Field(None, description="第三轮得分")
class ExamReportOverview(BaseModel):
"""成绩报告概览"""
avg_score: float = Field(..., description="平均成绩(基于round1_score)")
total_exams: int = Field(..., description="考试总数")
pass_rate: float = Field(..., description="及格率")
total_questions: int = Field(..., description="答题总数")
class ExamTrendItem(BaseModel):
"""成绩趋势项"""
date: str = Field(..., description="日期(YYYY-MM-DD)")
avg_score: float = Field(..., description="平均分")
class SubjectStatItem(BaseModel):
"""科目统计项"""
course_id: int = Field(..., description="课程ID")
course_name: str = Field(..., description="课程名称")
avg_score: float = Field(..., description="平均分")
exam_count: int = Field(..., description="考试次数")
max_score: float = Field(..., description="最高分")
min_score: float = Field(..., description="最低分")
pass_rate: float = Field(..., description="及格率")
class RecentExamItem(BaseModel):
"""最近考试记录项"""
id: int = Field(..., description="考试ID")
course_id: int = Field(..., description="课程ID")
course_name: str = Field(..., description="课程名称")
score: Optional[float] = Field(None, description="最终得分")
total_score: float = Field(..., description="总分")
is_passed: Optional[bool] = Field(None, description="是否通过")
duration_seconds: Optional[int] = Field(None, description="考试用时(秒)")
start_time: str = Field(..., description="开始时间")
end_time: Optional[str] = Field(None, description="结束时间")
round_scores: RoundScores = Field(..., description="三轮得分")
class ExamReportResponse(BaseModel):
"""成绩报告响应"""
overview: ExamReportOverview = Field(..., description="概览数据")
trends: List[ExamTrendItem] = Field(..., description="趋势数据")
subjects: List[SubjectStatItem] = Field(..., description="科目分析")
recent_exams: List[RecentExamItem] = Field(..., description="最近考试记录")
class MistakeListItem(BaseModel):
"""错题列表项"""
id: int = Field(..., description="错题记录ID")
exam_id: int = Field(..., description="考试ID")
course_id: int = Field(..., description="课程ID")
course_name: str = Field(..., description="课程名称")
question_content: str = Field(..., description="题目内容")
correct_answer: str = Field(..., description="正确答案")
user_answer: str = Field(..., description="用户答案")
question_type: Optional[str] = Field(None, description="题型")
knowledge_point_id: Optional[int] = Field(None, description="知识点ID")
knowledge_point_name: Optional[str] = Field(None, description="知识点名称")
created_at: datetime = Field(..., description="创建时间")
class MistakeListResponse(BaseModel):
"""错题列表响应"""
items: List[MistakeListItem] = Field(..., description="错题列表")
total: int = Field(..., description="总数")
page: int = Field(..., description="当前页")
size: int = Field(..., description="每页数量")
pages: int = Field(..., description="总页数")
class MistakeByCourse(BaseModel):
"""按课程统计错题"""
course_id: int = Field(..., description="课程ID")
course_name: str = Field(..., description="课程名称")
count: int = Field(..., description="错题数量")
class MistakeByType(BaseModel):
"""按题型统计错题"""
type: str = Field(..., description="题型代码")
type_name: str = Field(..., description="题型名称")
count: int = Field(..., description="错题数量")
class MistakeByTime(BaseModel):
"""按时间统计错题"""
week: int = Field(..., description="最近一周")
month: int = Field(..., description="最近一月")
quarter: int = Field(..., description="最近三月")
class MistakesStatisticsResponse(BaseModel):
"""错题统计响应"""
total: int = Field(..., description="错题总数")
by_course: List[MistakeByCourse] = Field(..., description="按课程统计")
by_type: List[MistakeByType] = Field(..., description="按题型统计")
by_time: MistakeByTime = Field(..., description="按时间统计")
class UpdateRoundScoreRequest(BaseModel):
"""更新轮次得分请求"""
round: int = Field(..., ge=1, le=3, description="轮次(1/2/3)")
score: float = Field(..., ge=0, le=100, description="得分")
is_final: bool = Field(False, description="是否为最终轮次(如果是,则同时更新总分和状态)")