yanlongqi 960f557ca4 添加每日一练功能(未完成排行榜前端)
后端功能:
- 添加Exam模型is_system字段标识系统试卷
- 创建每日一练服务,使用PostgreSQL分布式锁
- 集成cron定时任务,每天凌晨1点自动生成试卷
- 自动分享给所有用户(批量插入)
- API权限控制:系统试卷禁止删除和再次分享
- 添加GetDailyExamRanking API返回排行榜

前端功能:
- 添加is_system类型定义
- 系统试卷显示"系统"标签
- 系统试卷隐藏删除和分享按钮
- 添加getDailyExamRanking API方法

技术亮点:
- 使用PostgreSQL Advisory Lock实现分布式锁
- 使用robfig/cron/v3调度定时任务
- 批量插入提升分享性能

待完成:首页添加每日一练排行榜组件

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-02 00:26:51 +08:00

163 lines
7.9 KiB
Go
Raw Permalink 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.

package models
import (
"time"
"gorm.io/datatypes"
"gorm.io/gorm"
)
// Exam 试卷模型
type Exam struct {
ID uint `gorm:"primaryKey" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
UserID uint `gorm:"not null;index" json:"user_id"` // 创建者ID
Title string `gorm:"type:varchar(200);default:''" json:"title"` // 试卷标题
TotalScore int `gorm:"not null;default:100" json:"total_score"` // 总分
Duration int `gorm:"not null;default:60" json:"duration"` // 考试时长(分钟)
PassScore int `gorm:"not null;default:60" json:"pass_score"` // 及格分数
QuestionIDs datatypes.JSON `gorm:"type:json" json:"question_ids"` // 题目ID列表 (JSON数组)
Status string `gorm:"type:varchar(20);not null;default:'active'" json:"status"` // 状态: active, archived
IsSystem bool `gorm:"default:false;index" json:"is_system"` // 是否为系统试卷
// 关联关系
Shares []ExamShare `gorm:"foreignKey:ExamID" json:"-"` // 该试卷的分享记录(作为被分享试卷)
SharedToMe []ExamShare `gorm:"foreignKey:SharedToID" json:"-"` // 分享给我的记录(作为接收者)
}
// IsAccessibleBy 检查用户是否有权限访问试卷
func (e *Exam) IsAccessibleBy(userID int64, db *gorm.DB) bool {
// 用户是试卷创建者
if int64(e.UserID) == userID {
return true
}
// 检查是否被分享给该用户
var count int64
db.Model(&ExamShare{}).Where("exam_id = ? AND shared_to_id = ?", e.ID, userID).Count(&count)
return count > 0
}
// GetAccessibleExams 获取用户可访问的所有试卷(拥有的+被分享的)
func GetAccessibleExams(userID int64, db *gorm.DB) ([]Exam, error) {
var exams []Exam
// 子查询被分享的试卷ID
subQuery := db.Model(&ExamShare{}).Select("exam_id").Where("shared_to_id = ?", userID)
// 查询:用户拥有的 OR 被分享的试卷
err := db.Where("user_id = ? OR id IN (?)", uint(userID), subQuery).
Order("created_at DESC").
Find(&exams).Error
return exams, err
}
// ExamRecord 考试记录
type ExamRecord struct {
ID uint `gorm:"primaryKey" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
ExamID uint `gorm:"not null;index" json:"exam_id"` // 试卷ID
UserID uint `gorm:"not null;index" json:"user_id"` // 考生ID
StartTime *time.Time `json:"start_time"` // 开始时间
SubmitTime *time.Time `json:"submit_time"` // 提交时间
TimeSpent int `json:"time_spent"` // 实际用时(秒)
Score float64 `gorm:"type:decimal(5,2)" json:"score"` // 得分
TotalScore int `json:"total_score"` // 总分
Status string `gorm:"type:varchar(20);not null;default:'in_progress'" json:"status"` // 状态: in_progress, submitted, graded
IsPassed bool `json:"is_passed"` // 是否通过
// 关联
Exam *Exam `gorm:"foreignKey:ExamID" json:"exam,omitempty"`
User *User `gorm:"foreignKey:UserID" json:"user,omitempty"`
}
// ExamUserAnswer 用户答案表(记录每道题的答案)
type ExamUserAnswer struct {
ID int64 `gorm:"primaryKey" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
ExamRecordID int64 `gorm:"not null;index:idx_record_question" json:"exam_record_id"` // 考试记录ID
QuestionID int64 `gorm:"not null;index:idx_record_question" json:"question_id"` // 题目ID
UserID int64 `gorm:"not null;index" json:"user_id"` // 用户ID
Answer datatypes.JSON `gorm:"type:json" json:"answer"` // 用户答案 (JSON格式支持各种题型)
IsCorrect *bool `json:"is_correct,omitempty"` // 是否正确(提交后评分)
Score float64 `gorm:"type:decimal(5,2);default:0" json:"score"` // 得分
AIGradingData datatypes.JSON `gorm:"type:json" json:"ai_grading_data,omitempty"` // AI评分数据
AnsweredAt *time.Time `json:"answered_at"` // 答题时间
LastModifiedAt time.Time `json:"last_modified_at"` // 最后修改时间
// 关联
ExamRecord *ExamRecord `gorm:"foreignKey:ExamRecordID" json:"-"`
Question *PracticeQuestion `gorm:"foreignKey:QuestionID" json:"-"`
}
// ExamConfig 试卷配置结构
type ExamConfig struct {
QuestionTypes []QuestionTypeConfig `json:"question_types"` // 题型配置
Categories []string `json:"categories"` // 题目分类筛选
Difficulty []string `json:"difficulty"` // 难度筛选
RandomOrder bool `json:"random_order"` // 是否随机顺序
}
// QuestionTypeConfig 题型配置
type QuestionTypeConfig struct {
Type string `json:"type"` // 题目类型
Count int `json:"count"` // 题目数量
Score float64 `json:"score"` // 每题分数
}
// ExamAnswer 考试答案结构
type ExamAnswer struct {
QuestionID int64 `json:"question_id"`
Answer interface{} `json:"answer"` // 用户答案
CorrectAnswer interface{} `json:"correct_answer"` // 正确答案
IsCorrect bool `json:"is_correct"`
Score float64 `json:"score"`
AIGrading *AIGrading `json:"ai_grading,omitempty"`
}
// ExamQuestionConfig 考试题目配置
type ExamQuestionConfig struct {
FillInBlank int `json:"fill_in_blank"` // 填空题数量
TrueFalse int `json:"true_false"` // 判断题数量
MultipleChoice int `json:"multiple_choice"` // 单选题数量
MultipleSelection int `json:"multiple_selection"` // 多选题数量
ShortAnswer int `json:"short_answer"` // 简答题数量
OrdinaryEssay int `json:"ordinary_essay"` // 普通涉密人员论述题数量
ManagementEssay int `json:"management_essay"` // 保密管理人员论述题数量
}
// DefaultExamConfig 默认考试配置
var DefaultExamConfig = ExamQuestionConfig{
FillInBlank: 10, // 填空题10道
TrueFalse: 10, // 判断题10道
MultipleChoice: 10, // 单选题10道
MultipleSelection: 10, // 多选题10道
ShortAnswer: 2, // 简答题2道
OrdinaryEssay: 1, // 普通论述题1道
ManagementEssay: 1, // 管理论述题1道
}
// ExamScoreConfig 考试分值配置
type ExamScoreConfig struct {
FillInBlank float64 `json:"fill_in_blank"` // 填空题分值
TrueFalse float64 `json:"true_false"` // 判断题分值
MultipleChoice float64 `json:"multiple_choice"` // 单选题分值
MultipleSelection float64 `json:"multiple_selection"` // 多选题分值
Essay float64 `json:"essay"` // 论述题分值
}
// DefaultScoreConfig 默认分值配置
var DefaultScoreConfig = ExamScoreConfig{
FillInBlank: 2.0, // 填空题每题2分 (共20分)
TrueFalse: 2.0, // 判断题每题2分 (共20分)
MultipleChoice: 1.0, // 单选题每题1分 (共10分)
MultipleSelection: 2.5, // 多选题每题2.5分 (共25分)
Essay: 25.0, // 论述题25分
}