This commit is contained in:
2025-09-04 18:31:00 +08:00
parent 52c321ff57
commit 1064046016
79 changed files with 12873 additions and 7 deletions
+4 -2
View File
@@ -2,12 +2,14 @@ package service
import (
"github.com/flipped-aurora/gin-vue-admin/server/service/example"
"github.com/flipped-aurora/gin-vue-admin/server/service/learning"
"github.com/flipped-aurora/gin-vue-admin/server/service/system"
)
var ServiceGroupApp = new(ServiceGroup)
type ServiceGroup struct {
SystemServiceGroup system.ServiceGroup
ExampleServiceGroup example.ServiceGroup
SystemServiceGroup system.ServiceGroup
ExampleServiceGroup example.ServiceGroup
LearningServiceGroup learning.ServiceGroup
}
+126
View File
@@ -0,0 +1,126 @@
package learning
import (
"context"
"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 ChapterService struct{}
// CreateChapter 创建章节
func (chapterService *ChapterService) CreateChapter(ctx context.Context, chapter *learning.Chapter) (err error) {
err = global.GVA_DB.WithContext(ctx).Create(chapter).Error
return err
}
// DeleteChapter 删除章节
func (chapterService *ChapterService) DeleteChapter(ctx context.Context, ID string) (err error) {
err = global.GVA_DB.WithContext(ctx).Delete(&learning.Chapter{}, ID).Error
return err
}
// DeleteChapterByIds 批量删除章节
func (chapterService *ChapterService) DeleteChapterByIds(ctx context.Context, IDs []string) (err error) {
err = global.GVA_DB.WithContext(ctx).Delete(&[]learning.Chapter{}, "id in ?", IDs).Error
return err
}
// UpdateChapter 更新章节
func (chapterService *ChapterService) UpdateChapter(ctx context.Context, chapter learning.Chapter) (err error) {
err = global.GVA_DB.WithContext(ctx).Model(&learning.Chapter{}).Where("id = ?", chapter.ID).Updates(&chapter).Error
return err
}
// GetChapter 根据ID获取章节
func (chapterService *ChapterService) GetChapter(ctx context.Context, ID string) (chapter learning.Chapter, err error) {
err = global.GVA_DB.WithContext(ctx).Where("id = ?", ID).First(&chapter).Error
if err != nil {
return
}
// 手动查询关联的课程信息
if chapter.CourseId != 0 {
var course learning.Course
courseErr := global.GVA_DB.WithContext(ctx).Where("id = ?", chapter.CourseId).First(&course).Error
if courseErr == nil {
chapter.Course = course
}
}
return
}
// GetChapterInfoList 分页获取章节列表
func (chapterService *ChapterService) GetChapterInfoList(ctx context.Context, info learningReq.ChapterSearch) (list []learning.Chapter, total int64, err error) {
limit := info.PageSize
offset := info.PageSize * (info.Page - 1)
// 创建db
db := global.GVA_DB.WithContext(ctx).Model(&learning.Chapter{})
var chapters []learning.Chapter
// 如果有条件搜索 下方会自动创建搜索语句
if info.StartCreatedAt != nil && info.EndCreatedAt != nil {
db = db.Where("created_at BETWEEN ? AND ?", info.StartCreatedAt, info.EndCreatedAt)
}
if info.Title != "" {
db = db.Where("title LIKE ?", "%"+info.Title+"%")
}
if info.Status != "" {
db = db.Where("status = ?", info.Status)
}
if info.CourseId != 0 {
db = db.Where("course_id = ?", info.CourseId)
}
err = db.Count(&total).Error
if err != nil {
return
}
if limit != 0 {
db = db.Limit(limit).Offset(offset)
}
err = db.Order("sort asc, created_at desc").Find(&chapters).Error
if err != nil {
return chapters, total, err
}
// 手动查询关联的课程信息
courseIds := make([]uint, 0)
courseMap := make(map[uint]learning.Course)
for _, chapter := range chapters {
if chapter.CourseId != 0 {
courseIds = append(courseIds, chapter.CourseId)
}
}
if len(courseIds) > 0 {
var courses []learning.Course
global.GVA_DB.WithContext(ctx).Where("id in ?", courseIds).Find(&courses)
for _, course := range courses {
courseMap[course.ID] = course
}
// 为每个章节设置课程信息
for i := range chapters {
if course, exists := courseMap[chapters[i].CourseId]; exists {
chapters[i].Course = course
}
}
}
return chapters, total, err
}
// GetChaptersByCourse 根据课程ID获取章节列表
func (chapterService *ChapterService) GetChaptersByCourse(ctx context.Context, courseId uint) (list []learning.Chapter, err error) {
err = global.GVA_DB.WithContext(ctx).Where("course_id = ? AND status = ?", courseId, "published").Order("sort asc, created_at asc").Find(&list).Error
return
}
// UpdateChapterStatus 更新章节状态
func (chapterService *ChapterService) UpdateChapterStatus(ctx context.Context, ID string, status string) (err error) {
err = global.GVA_DB.WithContext(ctx).Model(&learning.Chapter{}).Where("id = ?", ID).Update("status", status).Error
return err
}
+86
View File
@@ -0,0 +1,86 @@
package learning
import (
"context"
"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 CourseService struct{}
// CreateCourse 创建课程
func (courseService *CourseService) CreateCourse(ctx context.Context, course *learning.Course) (err error) {
err = global.GVA_DB.WithContext(ctx).Create(course).Error
return err
}
// DeleteCourse 删除课程
func (courseService *CourseService) DeleteCourse(ctx context.Context, ID string) (err error) {
err = global.GVA_DB.WithContext(ctx).Delete(&learning.Course{}, ID).Error
return err
}
// DeleteCourseByIds 批量删除课程
func (courseService *CourseService) DeleteCourseByIds(ctx context.Context, IDs []string) (err error) {
err = global.GVA_DB.WithContext(ctx).Delete(&[]learning.Course{}, "id in ?", IDs).Error
return err
}
// UpdateCourse 更新课程
func (courseService *CourseService) UpdateCourse(ctx context.Context, course learning.Course) (err error) {
err = global.GVA_DB.WithContext(ctx).Model(&learning.Course{}).Where("id = ?", course.ID).Updates(&course).Error
return err
}
// GetCourse 根据ID获取课程
func (courseService *CourseService) GetCourse(ctx context.Context, ID string) (course learning.Course, err error) {
err = global.GVA_DB.WithContext(ctx).Where("id = ?", ID).First(&course).Error
return
}
// GetCourseInfoList 分页获取课程列表
func (courseService *CourseService) GetCourseInfoList(ctx context.Context, info learningReq.CourseSearch) (list []learning.Course, total int64, err error) {
limit := info.PageSize
offset := info.PageSize * (info.Page - 1)
// 创建db
db := global.GVA_DB.WithContext(ctx).Model(&learning.Course{})
var courses []learning.Course
// 如果有条件搜索 下方会自动创建搜索语句
if info.StartCreatedAt != nil && info.EndCreatedAt != nil {
db = db.Where("created_at BETWEEN ? AND ?", info.StartCreatedAt, info.EndCreatedAt)
}
if info.Title != "" {
db = db.Where("title LIKE ?", "%"+info.Title+"%")
}
if info.Status != "" {
db = db.Where("status = ?", info.Status)
}
if info.Difficulty != "" {
db = db.Where("difficulty = ?", info.Difficulty)
}
err = db.Count(&total).Error
if err != nil {
return
}
if limit != 0 {
db = db.Limit(limit).Offset(offset)
}
err = db.Order("sort desc, created_at desc").Find(&courses).Error
return courses, total, err
}
// GetPublishedCourses 获取已发布的课程列表(供学员选择)
func (courseService *CourseService) GetPublishedCourses(ctx context.Context) (list []learning.Course, err error) {
err = global.GVA_DB.WithContext(ctx).Where("status = ?", "published").Order("sort desc, created_at desc").Find(&list).Error
return
}
// UpdateCourseStatus 更新课程状态
func (courseService *CourseService) UpdateCourseStatus(ctx context.Context, ID string, status string) (err error) {
err = global.GVA_DB.WithContext(ctx).Model(&learning.Course{}).Where("id = ?", ID).Update("status", status).Error
return err
}
+12
View File
@@ -0,0 +1,12 @@
package learning
type ServiceGroup struct {
CourseService
ChapterService
KnowledgePointService
QuestionService
UserLearningService
WrongQuestionService
ExamService
UserExamService
}
+193
View File
@@ -0,0 +1,193 @@
package learning
import (
"context"
"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 ExamService struct{}
// CreateExam 创建考试
func (examService *ExamService) CreateExam(ctx context.Context, exam *learning.Exam) (err error) {
// 设置默认值
if exam.Status == "" {
exam.Status = "draft"
}
if exam.MaxAttempts == 0 {
exam.MaxAttempts = 1
}
err = global.GVA_DB.WithContext(ctx).Create(exam).Error
return err
}
// DeleteExam 删除考试
func (examService *ExamService) DeleteExam(ctx context.Context, ID string) (err error) {
err = global.GVA_DB.WithContext(ctx).Delete(&learning.Exam{}, ID).Error
return err
}
// DeleteExamByIds 批量删除考试
func (examService *ExamService) DeleteExamByIds(ctx context.Context, IDs []string) (err error) {
err = global.GVA_DB.WithContext(ctx).Delete(&[]learning.Exam{}, "id in ?", IDs).Error
return err
}
// UpdateExam 更新考试
func (examService *ExamService) UpdateExam(ctx context.Context, exam learning.Exam) (err error) {
err = global.GVA_DB.WithContext(ctx).Model(&learning.Exam{}).Where("id = ?", exam.ID).Updates(&exam).Error
return err
}
// GetExam 根据ID获取考试
func (examService *ExamService) GetExam(ctx context.Context, ID string) (exam learning.Exam, err error) {
err = global.GVA_DB.WithContext(ctx).Where("id = ?", ID).First(&exam).Error
if err != nil {
return
}
// 手动查询关联的课程信息
examService.loadCourseInfo(ctx, &exam)
return
}
// GetExamInfoList 分页获取考试列表
func (examService *ExamService) GetExamInfoList(ctx context.Context, info learningReq.ExamSearch) (list []learning.Exam, total int64, err error) {
limit := info.PageSize
offset := info.PageSize * (info.Page - 1)
// 创建db
db := global.GVA_DB.WithContext(ctx).Model(&learning.Exam{})
var exams []learning.Exam
// 如果有条件搜索 下方会自动创建搜索语句
if info.StartCreatedAt != nil && info.EndCreatedAt != nil {
db = db.Where("created_at BETWEEN ? AND ?", info.StartCreatedAt, info.EndCreatedAt)
}
if info.Title != "" {
db = db.Where("title LIKE ?", "%"+info.Title+"%")
}
if info.CourseId != 0 {
db = db.Where("course_id = ?", info.CourseId)
}
if info.Status != "" {
db = db.Where("status = ?", info.Status)
}
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(&exams).Error
if err != nil {
return exams, total, err
}
// 批量加载关联的课程信息
for i := range exams {
examService.loadCourseInfo(ctx, &exams[i])
}
return exams, total, err
}
// GetActiveExams 获取进行中的考试列表
func (examService *ExamService) GetActiveExams(ctx context.Context) (list []learning.Exam, err error) {
now := time.Now()
err = global.GVA_DB.WithContext(ctx).
Where("status = ? AND start_time <= ? AND end_time >= ?", "published", now, now).
Order("start_time asc").
Find(&list).Error
// 批量加载关联的课程信息
for i := range list {
examService.loadCourseInfo(ctx, &list[i])
}
return
}
// GetExamsByCourse 根据课程获取考试列表
func (examService *ExamService) GetExamsByCourse(ctx context.Context, courseId uint) (list []learning.Exam, err error) {
err = global.GVA_DB.WithContext(ctx).
Where("course_id = ? AND status = ?", courseId, "published").
Order("start_time asc").
Find(&list).Error
// 批量加载关联的课程信息
for i := range list {
examService.loadCourseInfo(ctx, &list[i])
}
return
}
// PublishExam 发布考试
func (examService *ExamService) PublishExam(ctx context.Context, examId uint) (err error) {
updates := map[string]interface{}{
"status": "published",
}
err = global.GVA_DB.WithContext(ctx).Model(&learning.Exam{}).
Where("id = ?", examId).
Updates(updates).Error
return err
}
// GetExamStatistics 获取考试统计
func (examService *ExamService) GetExamStatistics(ctx context.Context) (stats learningReq.ExamStatistics, err error) {
// 总考试数
var totalExams int64
err = global.GVA_DB.WithContext(ctx).Model(&learning.Exam{}).Count(&totalExams).Error
if err != nil {
return
}
stats.TotalExams = int(totalExams)
// 进行中的考试数
now := time.Now()
var activeExams int64
err = global.GVA_DB.WithContext(ctx).Model(&learning.Exam{}).
Where("status = ? AND start_time <= ? AND end_time >= ?", "published", now, now).
Count(&activeExams).Error
if err != nil {
return
}
stats.ActiveExams = int(activeExams)
// 已结束的考试数
var completedExams int64
err = global.GVA_DB.WithContext(ctx).Model(&learning.Exam{}).
Where("status = ? AND end_time < ?", "published", now).
Count(&completedExams).Error
if err != nil {
return
}
stats.CompletedExams = int(completedExams)
// 草稿考试数
var draftExams int64
err = global.GVA_DB.WithContext(ctx).Model(&learning.Exam{}).
Where("status = ?", "draft").
Count(&draftExams).Error
if err != nil {
return
}
stats.DraftExams = int(draftExams)
return
}
// loadCourseInfo 加载课程信息
func (examService *ExamService) loadCourseInfo(ctx context.Context, exam *learning.Exam) {
if exam.CourseId != 0 {
var course learning.Course
if err := global.GVA_DB.WithContext(ctx).Where("id = ?", exam.CourseId).First(&course).Error; err == nil {
exam.Course = course
}
}
}
+126
View File
@@ -0,0 +1,126 @@
package learning
import (
"context"
"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 KnowledgePointService struct{}
// CreateKnowledgePoint 创建知识点
func (knowledgePointService *KnowledgePointService) CreateKnowledgePoint(ctx context.Context, knowledgePoint *learning.KnowledgePoint) (err error) {
err = global.GVA_DB.WithContext(ctx).Create(knowledgePoint).Error
return err
}
// DeleteKnowledgePoint 删除知识点
func (knowledgePointService *KnowledgePointService) DeleteKnowledgePoint(ctx context.Context, ID string) (err error) {
err = global.GVA_DB.WithContext(ctx).Delete(&learning.KnowledgePoint{}, ID).Error
return err
}
// DeleteKnowledgePointByIds 批量删除知识点
func (knowledgePointService *KnowledgePointService) DeleteKnowledgePointByIds(ctx context.Context, IDs []string) (err error) {
err = global.GVA_DB.WithContext(ctx).Delete(&[]learning.KnowledgePoint{}, "id in ?", IDs).Error
return err
}
// UpdateKnowledgePoint 更新知识点
func (knowledgePointService *KnowledgePointService) UpdateKnowledgePoint(ctx context.Context, knowledgePoint learning.KnowledgePoint) (err error) {
err = global.GVA_DB.WithContext(ctx).Model(&learning.KnowledgePoint{}).Where("id = ?", knowledgePoint.ID).Updates(&knowledgePoint).Error
return err
}
// GetKnowledgePoint 根据ID获取知识点
func (knowledgePointService *KnowledgePointService) GetKnowledgePoint(ctx context.Context, ID string) (knowledgePoint learning.KnowledgePoint, err error) {
err = global.GVA_DB.WithContext(ctx).Where("id = ?", ID).First(&knowledgePoint).Error
if err != nil {
return
}
// 手动查询关联的章节信息
if knowledgePoint.ChapterId != 0 {
var chapter learning.Chapter
chapterErr := global.GVA_DB.WithContext(ctx).Where("id = ?", knowledgePoint.ChapterId).First(&chapter).Error
if chapterErr == nil {
knowledgePoint.Chapter = chapter
}
}
return
}
// GetKnowledgePointInfoList 分页获取知识点列表
func (knowledgePointService *KnowledgePointService) GetKnowledgePointInfoList(ctx context.Context, info learningReq.KnowledgePointSearch) (list []learning.KnowledgePoint, total int64, err error) {
limit := info.PageSize
offset := info.PageSize * (info.Page - 1)
// 创建db
db := global.GVA_DB.WithContext(ctx).Model(&learning.KnowledgePoint{})
var knowledgePoints []learning.KnowledgePoint
// 如果有条件搜索 下方会自动创建搜索语句
if info.StartCreatedAt != nil && info.EndCreatedAt != nil {
db = db.Where("created_at BETWEEN ? AND ?", info.StartCreatedAt, info.EndCreatedAt)
}
if info.Title != "" {
db = db.Where("title LIKE ?", "%"+info.Title+"%")
}
if info.Status != "" {
db = db.Where("status = ?", info.Status)
}
if info.ChapterId != 0 {
db = db.Where("chapter_id = ?", info.ChapterId)
}
err = db.Count(&total).Error
if err != nil {
return
}
if limit != 0 {
db = db.Limit(limit).Offset(offset)
}
err = db.Order("sort asc, created_at desc").Find(&knowledgePoints).Error
if err != nil {
return knowledgePoints, total, err
}
// 手动查询关联的章节信息
chapterIds := make([]uint, 0)
chapterMap := make(map[uint]learning.Chapter)
for _, kp := range knowledgePoints {
if kp.ChapterId != 0 {
chapterIds = append(chapterIds, kp.ChapterId)
}
}
if len(chapterIds) > 0 {
var chapters []learning.Chapter
global.GVA_DB.WithContext(ctx).Where("id in ?", chapterIds).Find(&chapters)
for _, chapter := range chapters {
chapterMap[chapter.ID] = chapter
}
// 为每个知识点设置章节信息
for i := range knowledgePoints {
if chapter, exists := chapterMap[knowledgePoints[i].ChapterId]; exists {
knowledgePoints[i].Chapter = chapter
}
}
}
return knowledgePoints, total, err
}
// GetKnowledgePointsByChapter 根据章节ID获取知识点列表
func (knowledgePointService *KnowledgePointService) GetKnowledgePointsByChapter(ctx context.Context, chapterId uint) (list []learning.KnowledgePoint, err error) {
err = global.GVA_DB.WithContext(ctx).Where("chapter_id = ? AND status = ?", chapterId, "published").Order("sort asc, created_at asc").Find(&list).Error
return
}
// UpdateKnowledgePointStatus 更新知识点状态
func (knowledgePointService *KnowledgePointService) UpdateKnowledgePointStatus(ctx context.Context, ID string, status string) (err error) {
err = global.GVA_DB.WithContext(ctx).Model(&learning.KnowledgePoint{}).Where("id = ?", ID).Update("status", status).Error
return err
}
+129
View File
@@ -0,0 +1,129 @@
package learning
import (
"context"
"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 QuestionService struct{}
// CreateQuestion 创建题目
func (questionService *QuestionService) CreateQuestion(ctx context.Context, question *learning.Question) (err error) {
err = global.GVA_DB.WithContext(ctx).Create(question).Error
return err
}
// DeleteQuestion 删除题目
func (questionService *QuestionService) DeleteQuestion(ctx context.Context, ID string) (err error) {
err = global.GVA_DB.WithContext(ctx).Delete(&learning.Question{}, ID).Error
return err
}
// DeleteQuestionByIds 批量删除题目
func (questionService *QuestionService) DeleteQuestionByIds(ctx context.Context, IDs []string) (err error) {
err = global.GVA_DB.WithContext(ctx).Delete(&[]learning.Question{}, "id in ?", IDs).Error
return err
}
// UpdateQuestion 更新题目
func (questionService *QuestionService) UpdateQuestion(ctx context.Context, question learning.Question) (err error) {
err = global.GVA_DB.WithContext(ctx).Model(&learning.Question{}).Where("id = ?", question.ID).Updates(&question).Error
return err
}
// GetQuestion 根据ID获取题目
func (questionService *QuestionService) GetQuestion(ctx context.Context, ID string) (question learning.Question, err error) {
err = global.GVA_DB.WithContext(ctx).Where("id = ?", ID).First(&question).Error
if err != nil {
return
}
// 手动查询关联的知识点信息
if question.KnowledgePointId != 0 {
var knowledgePoint learning.KnowledgePoint
kpErr := global.GVA_DB.WithContext(ctx).Where("id = ?", question.KnowledgePointId).First(&knowledgePoint).Error
if kpErr == nil {
question.KnowledgePoint = knowledgePoint
}
}
return
}
// GetQuestionInfoList 分页获取题目列表
func (questionService *QuestionService) GetQuestionInfoList(ctx context.Context, info learningReq.QuestionSearch) (list []learning.Question, total int64, err error) {
limit := info.PageSize
offset := info.PageSize * (info.Page - 1)
// 创建db
db := global.GVA_DB.WithContext(ctx).Model(&learning.Question{})
var questions []learning.Question
// 如果有条件搜索 下方会自动创建搜索语句
if info.StartCreatedAt != nil && info.EndCreatedAt != nil {
db = db.Where("created_at BETWEEN ? AND ?", info.StartCreatedAt, info.EndCreatedAt)
}
if info.Title != "" {
db = db.Where("title LIKE ?", "%"+info.Title+"%")
}
if info.Type != "" {
db = db.Where("type = ?", info.Type)
}
if info.Difficulty != "" {
db = db.Where("difficulty = ?", info.Difficulty)
}
if info.KnowledgePointId != 0 {
db = db.Where("knowledge_point_id = ?", info.KnowledgePointId)
}
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(&questions).Error
if err != nil {
return questions, total, err
}
// 手动查询关联的知识点信息
kpIds := make([]uint, 0)
kpMap := make(map[uint]learning.KnowledgePoint)
for _, q := range questions {
if q.KnowledgePointId != 0 {
kpIds = append(kpIds, q.KnowledgePointId)
}
}
if len(kpIds) > 0 {
var knowledgePoints []learning.KnowledgePoint
global.GVA_DB.WithContext(ctx).Where("id in ?", kpIds).Find(&knowledgePoints)
for _, kp := range knowledgePoints {
kpMap[kp.ID] = kp
}
// 为每个题目设置知识点信息
for i := range questions {
if kp, exists := kpMap[questions[i].KnowledgePointId]; exists {
questions[i].KnowledgePoint = kp
}
}
}
return questions, total, err
}
// GetQuestionsByKnowledgePoint 根据知识点ID获取题目列表
func (questionService *QuestionService) GetQuestionsByKnowledgePoint(ctx context.Context, knowledgePointId uint) (list []learning.Question, err error) {
err = global.GVA_DB.WithContext(ctx).Where("knowledge_point_id = ?", knowledgePointId).Order("created_at asc").Find(&list).Error
return
}
// GetRandomQuestionsByKnowledgePoint 根据知识点ID随机获取题目
func (questionService *QuestionService) GetRandomQuestionsByKnowledgePoint(ctx context.Context, knowledgePointId uint, count int) (list []learning.Question, err error) {
err = global.GVA_DB.WithContext(ctx).Where("knowledge_point_id = ?", knowledgePointId).Order("RAND()").Limit(count).Find(&list).Error
return
}
+327
View File
@@ -0,0 +1,327 @@
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
}
}
}
+202
View File
@@ -0,0 +1,202 @@
package learning
import (
"context"
"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 UserLearningService struct{}
// CreateUserLearning 创建用户学习记录
func (userLearningService *UserLearningService) CreateUserLearning(ctx context.Context, userLearning *learning.UserLearning) (err error) {
// 检查是否已存在该用户对该课程的学习记录
var existingRecord learning.UserLearning
err = global.GVA_DB.WithContext(ctx).Where("user_id = ? AND course_id = ?", userLearning.UserId, userLearning.CourseId).First(&existingRecord).Error
if err == nil {
// 如果已存在,更新记录
return userLearningService.UpdateUserLearning(ctx, *userLearning)
}
// 设置默认值
if userLearning.LastStudyAt.IsZero() {
userLearning.LastStudyAt = time.Now()
}
if userLearning.Status == "" {
userLearning.Status = "learning"
}
err = global.GVA_DB.WithContext(ctx).Create(userLearning).Error
return err
}
// DeleteUserLearning 删除用户学习记录
func (userLearningService *UserLearningService) DeleteUserLearning(ctx context.Context, ID string) (err error) {
err = global.GVA_DB.WithContext(ctx).Delete(&learning.UserLearning{}, ID).Error
return err
}
// DeleteUserLearningByIds 批量删除用户学习记录
func (userLearningService *UserLearningService) DeleteUserLearningByIds(ctx context.Context, IDs []string) (err error) {
err = global.GVA_DB.WithContext(ctx).Delete(&[]learning.UserLearning{}, "id in ?", IDs).Error
return err
}
// UpdateUserLearning 更新用户学习记录
func (userLearningService *UserLearningService) UpdateUserLearning(ctx context.Context, userLearning learning.UserLearning) (err error) {
// 更新最后学习时间
userLearning.LastStudyAt = time.Now()
// 如果进度达到100%,设置完成状态和完成时间
if userLearning.Progress >= 100 && userLearning.Status != "completed" {
userLearning.Status = "completed"
userLearning.CompletedAt = time.Now()
}
err = global.GVA_DB.WithContext(ctx).Model(&learning.UserLearning{}).Where("id = ?", userLearning.ID).Updates(&userLearning).Error
return err
}
// GetUserLearning 根据ID获取用户学习记录
func (userLearningService *UserLearningService) GetUserLearning(ctx context.Context, ID string) (userLearning learning.UserLearning, err error) {
err = global.GVA_DB.WithContext(ctx).Where("id = ?", ID).First(&userLearning).Error
if err != nil {
return
}
// 手动查询关联信息
userLearningService.loadAssociations(ctx, &userLearning)
return
}
// GetUserLearningInfoList 分页获取用户学习记录列表
func (userLearningService *UserLearningService) GetUserLearningInfoList(ctx context.Context, info learningReq.UserLearningSearch) (list []learning.UserLearning, total int64, err error) {
limit := info.PageSize
offset := info.PageSize * (info.Page - 1)
// 创建db
db := global.GVA_DB.WithContext(ctx).Model(&learning.UserLearning{})
var userLearnings []learning.UserLearning
// 如果有条件搜索 下方会自动创建搜索语句
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.CourseId != 0 {
db = db.Where("course_id = ?", info.CourseId)
}
if info.Status != "" {
db = db.Where("status = ?", info.Status)
}
err = db.Count(&total).Error
if err != nil {
return
}
if limit != 0 {
db = db.Limit(limit).Offset(offset)
}
err = db.Order("last_study_at desc").Find(&userLearnings).Error
if err != nil {
return userLearnings, total, err
}
// 批量加载关联信息
for i := range userLearnings {
userLearningService.loadAssociations(ctx, &userLearnings[i])
}
return userLearnings, total, err
}
// GetUserProgress 获取用户学习进度
func (userLearningService *UserLearningService) GetUserProgress(ctx context.Context, userId uint, courseId uint) (progress []learning.UserLearning, err error) {
db := global.GVA_DB.WithContext(ctx).Where("user_id = ?", userId)
if courseId != 0 {
db = db.Where("course_id = ?", courseId)
}
err = db.Find(&progress).Error
// 批量加载关联信息
for i := range progress {
userLearningService.loadAssociations(ctx, &progress[i])
}
return
}
// GetLearningStatistics 获取用户学习统计
func (userLearningService *UserLearningService) GetLearningStatistics(ctx context.Context, userId uint) (stats learningReq.LearningStatistics, err error) {
// 总课程数
var totalCourses int64
err = global.GVA_DB.WithContext(ctx).Model(&learning.UserLearning{}).Where("user_id = ?", userId).Count(&totalCourses).Error
if err != nil {
return
}
stats.TotalCourses = int(totalCourses)
// 已完成课程数
var completedCourses int64
err = global.GVA_DB.WithContext(ctx).Model(&learning.UserLearning{}).Where("user_id = ? AND status = ?", userId, "completed").Count(&completedCourses).Error
if err != nil {
return
}
stats.CompletedCourses = int(completedCourses)
// 学习中课程数
var inProgressCourses int64
err = global.GVA_DB.WithContext(ctx).Model(&learning.UserLearning{}).Where("user_id = ? AND status = ?", userId, "learning").Count(&inProgressCourses).Error
if err != nil {
return
}
stats.InProgressCourses = int(inProgressCourses)
// 总学习时长和平均分数
var result struct {
TotalStudyTime int
AverageScore int
}
err = global.GVA_DB.WithContext(ctx).Model(&learning.UserLearning{}).
Select("SUM(study_time) as total_study_time, AVG(score) as average_score").
Where("user_id = ?", userId).
Scan(&result).Error
if err != nil {
return
}
stats.TotalStudyTime = result.TotalStudyTime
stats.AverageScore = result.AverageScore
return
}
// loadAssociations 加载关联信息
func (userLearningService *UserLearningService) loadAssociations(ctx context.Context, userLearning *learning.UserLearning) {
// 加载课程信息
if userLearning.CourseId != 0 {
var course learning.Course
if err := global.GVA_DB.WithContext(ctx).Where("id = ?", userLearning.CourseId).First(&course).Error; err == nil {
userLearning.Course = course
}
}
// 加载章节信息
if userLearning.ChapterId != 0 {
var chapter learning.Chapter
if err := global.GVA_DB.WithContext(ctx).Where("id = ?", userLearning.ChapterId).First(&chapter).Error; err == nil {
userLearning.Chapter = chapter
}
}
// 加载知识点信息
if userLearning.KnowledgePointId != 0 {
var knowledgePoint learning.KnowledgePoint
if err := global.GVA_DB.WithContext(ctx).Where("id = ?", userLearning.KnowledgePointId).First(&knowledgePoint).Error; err == nil {
userLearning.KnowledgePoint = knowledgePoint
}
}
}
+186
View File
@@ -0,0 +1,186 @@
package learning
import (
"context"
"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 WrongQuestionService struct{}
// CreateWrongQuestion 创建错题记录
func (wrongQuestionService *WrongQuestionService) CreateWrongQuestion(ctx context.Context, wrongQuestion *learning.WrongQuestion) (err error) {
// 检查是否已存在该用户对该题目的错题记录
var existingRecord learning.WrongQuestion
err = global.GVA_DB.WithContext(ctx).Where("user_id = ? AND question_id = ?", wrongQuestion.UserId, wrongQuestion.QuestionId).First(&existingRecord).Error
if err == nil {
// 如果已存在,更新错误次数和最后错误时间
existingRecord.WrongCount++
existingRecord.LastWrongAt = time.Now()
existingRecord.WrongAnswer = wrongQuestion.WrongAnswer
existingRecord.IsMastered = false // 重新答错,标记为未掌握
existingRecord.MasteredAt = time.Time{}
return wrongQuestionService.UpdateWrongQuestion(ctx, existingRecord)
}
// 设置默认值
if wrongQuestion.LastWrongAt.IsZero() {
wrongQuestion.LastWrongAt = time.Now()
}
if wrongQuestion.WrongCount == 0 {
wrongQuestion.WrongCount = 1
}
err = global.GVA_DB.WithContext(ctx).Create(wrongQuestion).Error
return err
}
// DeleteWrongQuestion 删除错题记录
func (wrongQuestionService *WrongQuestionService) DeleteWrongQuestion(ctx context.Context, ID string) (err error) {
err = global.GVA_DB.WithContext(ctx).Delete(&learning.WrongQuestion{}, ID).Error
return err
}
// DeleteWrongQuestionByIds 批量删除错题记录
func (wrongQuestionService *WrongQuestionService) DeleteWrongQuestionByIds(ctx context.Context, IDs []string) (err error) {
err = global.GVA_DB.WithContext(ctx).Delete(&[]learning.WrongQuestion{}, "id in ?", IDs).Error
return err
}
// UpdateWrongQuestion 更新错题记录
func (wrongQuestionService *WrongQuestionService) UpdateWrongQuestion(ctx context.Context, wrongQuestion learning.WrongQuestion) (err error) {
err = global.GVA_DB.WithContext(ctx).Model(&learning.WrongQuestion{}).Where("id = ?", wrongQuestion.ID).Updates(&wrongQuestion).Error
return err
}
// GetWrongQuestion 根据ID获取错题记录
func (wrongQuestionService *WrongQuestionService) GetWrongQuestion(ctx context.Context, ID string) (wrongQuestion learning.WrongQuestion, err error) {
err = global.GVA_DB.WithContext(ctx).Where("id = ?", ID).First(&wrongQuestion).Error
if err != nil {
return
}
// 手动查询关联的题目信息
wrongQuestionService.loadQuestionInfo(ctx, &wrongQuestion)
return
}
// GetWrongQuestionInfoList 分页获取错题记录列表
func (wrongQuestionService *WrongQuestionService) GetWrongQuestionInfoList(ctx context.Context, info learningReq.WrongQuestionSearch) (list []learning.WrongQuestion, total int64, err error) {
limit := info.PageSize
offset := info.PageSize * (info.Page - 1)
// 创建db
db := global.GVA_DB.WithContext(ctx).Model(&learning.WrongQuestion{})
var wrongQuestions []learning.WrongQuestion
// 如果有条件搜索 下方会自动创建搜索语句
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.QuestionId != 0 {
db = db.Where("question_id = ?", info.QuestionId)
}
if info.IsMastered {
db = db.Where("is_mastered = ?", info.IsMastered)
}
err = db.Count(&total).Error
if err != nil {
return
}
if limit != 0 {
db = db.Limit(limit).Offset(offset)
}
err = db.Order("last_wrong_at desc").Find(&wrongQuestions).Error
if err != nil {
return wrongQuestions, total, err
}
// 批量加载关联的题目信息
for i := range wrongQuestions {
wrongQuestionService.loadQuestionInfo(ctx, &wrongQuestions[i])
}
return wrongQuestions, total, err
}
// GetUserWrongQuestions 获取用户的错题列表
func (wrongQuestionService *WrongQuestionService) GetUserWrongQuestions(ctx context.Context, userId uint, onlyUnmastered bool) (list []learning.WrongQuestion, err error) {
db := global.GVA_DB.WithContext(ctx).Where("user_id = ?", userId)
if onlyUnmastered {
db = db.Where("is_mastered = ?", false)
}
err = db.Order("last_wrong_at desc").Find(&list).Error
// 批量加载关联的题目信息
for i := range list {
wrongQuestionService.loadQuestionInfo(ctx, &list[i])
}
return
}
// MarkAsMastered 标记题目为已掌握
func (wrongQuestionService *WrongQuestionService) MarkAsMastered(ctx context.Context, userId uint, wrongQuestionId uint) (err error) {
updates := map[string]interface{}{
"is_mastered": true,
"mastered_at": time.Now(),
}
err = global.GVA_DB.WithContext(ctx).Model(&learning.WrongQuestion{}).
Where("id = ? AND user_id = ?", wrongQuestionId, userId).
Updates(updates).Error
return err
}
// GetWrongQuestionStatistics 获取用户错题统计
func (wrongQuestionService *WrongQuestionService) GetWrongQuestionStatistics(ctx context.Context, userId uint) (stats learningReq.WrongQuestionStatistics, err error) {
// 总错题数
var totalWrongQuestions int64
err = global.GVA_DB.WithContext(ctx).Model(&learning.WrongQuestion{}).Where("user_id = ?", userId).Count(&totalWrongQuestions).Error
if err != nil {
return
}
stats.TotalWrongQuestions = int(totalWrongQuestions)
// 已掌握题目数
var masteredQuestions int64
err = global.GVA_DB.WithContext(ctx).Model(&learning.WrongQuestion{}).Where("user_id = ? AND is_mastered = ?", userId, true).Count(&masteredQuestions).Error
if err != nil {
return
}
stats.MasteredQuestions = int(masteredQuestions)
// 未掌握题目数
stats.UnmasteredQuestions = stats.TotalWrongQuestions - stats.MasteredQuestions
// 平均错误次数
var result struct {
AverageWrongCount float64
}
err = global.GVA_DB.WithContext(ctx).Model(&learning.WrongQuestion{}).
Select("AVG(wrong_count) as average_wrong_count").
Where("user_id = ?", userId).
Scan(&result).Error
if err != nil {
return
}
stats.AverageWrongCount = int(result.AverageWrongCount)
return
}
// loadQuestionInfo 加载题目信息
func (wrongQuestionService *WrongQuestionService) loadQuestionInfo(ctx context.Context, wrongQuestion *learning.WrongQuestion) {
if wrongQuestion.QuestionId != 0 {
var question learning.Question
if err := global.GVA_DB.WithContext(ctx).Where("id = ?", wrongQuestion.QuestionId).First(&question).Error; err == nil {
wrongQuestion.Question = question
}
}
}