Study/server/service/learning/user_exam.go

328 lines
9.3 KiB
Go

package learning
import (
"context"
"errors"
"time"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/learning"
learningReq "github.com/flipped-aurora/gin-vue-admin/server/model/learning/request"
)
type UserExamService struct{}
// CreateUserExam 创建用户考试记录
func (userExamService *UserExamService) CreateUserExam(ctx context.Context, userExam *learning.UserExam) (err error) {
// 设置默认值
if userExam.Status == "" {
userExam.Status = "in_progress"
}
if userExam.AttemptNumber == 0 {
userExam.AttemptNumber = 1
}
if userExam.StartTime.IsZero() {
userExam.StartTime = time.Now()
}
err = global.GVA_DB.WithContext(ctx).Create(userExam).Error
return err
}
// DeleteUserExam 删除用户考试记录
func (userExamService *UserExamService) DeleteUserExam(ctx context.Context, ID string) (err error) {
err = global.GVA_DB.WithContext(ctx).Delete(&learning.UserExam{}, ID).Error
return err
}
// DeleteUserExamByIds 批量删除用户考试记录
func (userExamService *UserExamService) DeleteUserExamByIds(ctx context.Context, IDs []string) (err error) {
err = global.GVA_DB.WithContext(ctx).Delete(&[]learning.UserExam{}, "id in ?", IDs).Error
return err
}
// UpdateUserExam 更新用户考试记录
func (userExamService *UserExamService) UpdateUserExam(ctx context.Context, userExam learning.UserExam) (err error) {
err = global.GVA_DB.WithContext(ctx).Model(&learning.UserExam{}).Where("id = ?", userExam.ID).Updates(&userExam).Error
return err
}
// GetUserExam 根据ID获取用户考试记录
func (userExamService *UserExamService) GetUserExam(ctx context.Context, ID string) (userExam learning.UserExam, err error) {
err = global.GVA_DB.WithContext(ctx).Where("id = ?", ID).First(&userExam).Error
if err != nil {
return
}
// 手动查询关联的考试信息
userExamService.loadExamInfo(ctx, &userExam)
return
}
// GetUserExamInfoList 分页获取用户考试记录列表
func (userExamService *UserExamService) GetUserExamInfoList(ctx context.Context, info learningReq.UserExamSearch) (list []learning.UserExam, total int64, err error) {
limit := info.PageSize
offset := info.PageSize * (info.Page - 1)
// 创建db
db := global.GVA_DB.WithContext(ctx).Model(&learning.UserExam{})
var userExams []learning.UserExam
// 如果有条件搜索 下方会自动创建搜索语句
if info.StartCreatedAt != nil && info.EndCreatedAt != nil {
db = db.Where("created_at BETWEEN ? AND ?", info.StartCreatedAt, info.EndCreatedAt)
}
if info.UserId != 0 {
db = db.Where("user_id = ?", info.UserId)
}
if info.ExamId != 0 {
db = db.Where("exam_id = ?", info.ExamId)
}
if info.Status != "" {
db = db.Where("status = ?", info.Status)
}
if info.IsPassed {
db = db.Where("is_passed = ?", info.IsPassed)
}
err = db.Count(&total).Error
if err != nil {
return
}
if limit != 0 {
db = db.Limit(limit).Offset(offset)
}
err = db.Order("created_at desc").Find(&userExams).Error
if err != nil {
return userExams, total, err
}
// 批量加载关联的考试信息
for i := range userExams {
userExamService.loadExamInfo(ctx, &userExams[i])
}
return userExams, total, err
}
// StartExam 开始考试
func (userExamService *UserExamService) StartExam(ctx context.Context, userId uint, examId uint) (userExam learning.UserExam, err error) {
// 检查考试是否存在且可以参加
var exam learning.Exam
err = global.GVA_DB.WithContext(ctx).Where("id = ? AND status = ?", examId, "published").First(&exam).Error
if err != nil {
return userExam, errors.New("考试不存在或未发布")
}
// 检查考试时间
now := time.Now()
if now.Before(exam.StartTime) {
return userExam, errors.New("考试尚未开始")
}
if now.After(exam.EndTime) {
return userExam, errors.New("考试已结束")
}
// 检查用户是否已经参加过考试
var existingCount int64
global.GVA_DB.WithContext(ctx).Model(&learning.UserExam{}).
Where("user_id = ? AND exam_id = ?", userId, examId).
Count(&existingCount)
// 检查是否允许重考和尝试次数
if existingCount > 0 && !exam.AllowRetake {
return userExam, errors.New("该考试不允许重考")
}
if existingCount >= int64(exam.MaxAttempts) {
return userExam, errors.New("已达到最大尝试次数")
}
// 创建考试记录
userExam = learning.UserExam{
UserId: userId,
ExamId: examId,
AttemptNumber: int(existingCount) + 1,
StartTime: now,
Status: "in_progress",
}
err = userExamService.CreateUserExam(ctx, &userExam)
if err != nil {
return userExam, err
}
// 加载考试信息
userExamService.loadExamInfo(ctx, &userExam)
return userExam, nil
}
// SubmitExam 提交考试
func (userExamService *UserExamService) SubmitExam(ctx context.Context, userExamId uint, totalScore int, correctCount int, wrongCount int) (err error) {
var userExam learning.UserExam
err = global.GVA_DB.WithContext(ctx).Where("id = ?", userExamId).First(&userExam).Error
if err != nil {
return errors.New("考试记录不存在")
}
if userExam.Status != "in_progress" {
return errors.New("考试已结束,无法重复提交")
}
// 获取考试信息
var exam learning.Exam
err = global.GVA_DB.WithContext(ctx).Where("id = ?", userExam.ExamId).First(&exam).Error
if err != nil {
return err
}
// 计算考试时长
now := time.Now()
duration := int(now.Sub(userExam.StartTime).Minutes())
// 判断是否通过
isPassed := totalScore >= exam.PassScore
// 更新考试记录
updates := map[string]interface{}{
"end_time": now,
"submitted_at": now,
"duration": duration,
"total_score": totalScore,
"correct_count": correctCount,
"wrong_count": wrongCount,
"status": "completed",
"is_passed": isPassed,
}
err = global.GVA_DB.WithContext(ctx).Model(&learning.UserExam{}).
Where("id = ?", userExamId).
Updates(updates).Error
return err
}
// GetUserExamHistory 获取用户考试历史
func (userExamService *UserExamService) GetUserExamHistory(ctx context.Context, userId uint) (list []learning.UserExam, err error) {
err = global.GVA_DB.WithContext(ctx).
Where("user_id = ?", userId).
Order("created_at desc").
Find(&list).Error
// 批量加载关联的考试信息
for i := range list {
userExamService.loadExamInfo(ctx, &list[i])
}
return
}
// GetUserExamStatistics 获取用户考试统计
func (userExamService *UserExamService) GetUserExamStatistics(ctx context.Context, userId uint) (stats learningReq.UserExamStatistics, err error) {
// 总考试次数
var totalExams int64
err = global.GVA_DB.WithContext(ctx).Model(&learning.UserExam{}).
Where("user_id = ? AND status = ?", userId, "completed").
Count(&totalExams).Error
if err != nil {
return
}
stats.TotalExams = int(totalExams)
if totalExams == 0 {
return stats, nil
}
// 通过考试次数
var passedExams int64
err = global.GVA_DB.WithContext(ctx).Model(&learning.UserExam{}).
Where("user_id = ? AND status = ? AND is_passed = ?", userId, "completed", true).
Count(&passedExams).Error
if err != nil {
return
}
stats.PassedExams = int(passedExams)
stats.FailedExams = stats.TotalExams - stats.PassedExams
// 平均分数和最高分数
var result struct {
AverageScore float64
HighestScore int
TotalTime int
}
err = global.GVA_DB.WithContext(ctx).Model(&learning.UserExam{}).
Select("AVG(total_score) as average_score, MAX(total_score) as highest_score, SUM(duration) as total_time").
Where("user_id = ? AND status = ?", userId, "completed").
Scan(&result).Error
if err != nil {
return
}
stats.AverageScore = result.AverageScore
stats.HighestScore = result.HighestScore
stats.TotalStudyTime = result.TotalTime
// 通过率
if stats.TotalExams > 0 {
stats.PassRate = float64(stats.PassedExams) / float64(stats.TotalExams) * 100
}
return
}
// GetExamRanking 获取考试排行榜
func (userExamService *UserExamService) GetExamRanking(ctx context.Context, examId uint, limit int) (ranking learningReq.ExamRankingResponse, err error) {
// 获取考试信息
var exam learning.Exam
err = global.GVA_DB.WithContext(ctx).Where("id = ?", examId).First(&exam).Error
if err != nil {
return
}
ranking.ExamId = examId
ranking.ExamTitle = exam.Title
// 获取排行榜数据
var userExams []learning.UserExam
db := global.GVA_DB.WithContext(ctx).
Where("exam_id = ? AND status = ?", examId, "completed").
Order("total_score desc, duration asc")
if limit > 0 {
db = db.Limit(limit)
}
err = db.Find(&userExams).Error
if err != nil {
return
}
// 构建排行榜
for i, userExam := range userExams {
item := learningReq.ExamRankingItem{
UserId: userExam.UserId,
Score: userExam.TotalScore,
Duration: userExam.Duration,
Rank: i + 1,
IsPassed: userExam.IsPassed,
SubmittedAt: userExam.SubmittedAt,
}
ranking.Rankings = append(ranking.Rankings, item)
}
// 获取总数
global.GVA_DB.WithContext(ctx).Model(&learning.UserExam{}).
Where("exam_id = ? AND status = ?", examId, "completed").
Count(&ranking.Total)
return
}
// loadExamInfo 加载考试信息
func (userExamService *UserExamService) loadExamInfo(ctx context.Context, userExam *learning.UserExam) {
if userExam.ExamId != 0 {
var exam learning.Exam
if err := global.GVA_DB.WithContext(ctx).Where("id = ?", userExam.ExamId).First(&exam).Error; err == nil {
userExam.Exam = exam
}
}
}