328 lines
9.3 KiB
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
|
|
}
|
|
}
|
|
}
|