AnCao/internal/handlers/exam_grading.go

270 lines
7.6 KiB
Go
Raw 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 handlers
import (
"ankao/internal/database"
"ankao/internal/models"
"ankao/internal/services"
"encoding/json"
"fmt"
"log"
"gorm.io/datatypes"
)
// ReGradeExam 公开的重新阅卷函数,可被外部调用
func ReGradeExam(recordID uint, examID uint, userID uint) {
gradeExam(recordID, examID, userID)
}
// gradeExam 异步阅卷函数
func gradeExam(recordID uint, examID uint, userID uint) {
db := database.GetDB()
// 查询考试记录
var record models.ExamRecord
if err := db.Where("id = ?", recordID).First(&record).Error; err != nil {
log.Printf("查询考试记录失败: %v", err)
return
}
// 查询试卷
var exam models.Exam
if err := db.Where("id = ?", examID).First(&exam).Error; err != nil {
log.Printf("查询试卷失败: %v", err)
return
}
// 从 ExamUserAnswer 表读取所有答案
var userAnswers []models.ExamUserAnswer
if err := db.Where("exam_record_id = ?", recordID).Find(&userAnswers).Error; err != nil {
log.Printf("查询用户答案失败: %v", err)
return
}
// 转换为 map 格式方便查找
answersMap := make(map[int64]interface{})
for _, ua := range userAnswers {
var answer interface{}
if err := json.Unmarshal(ua.Answer, &answer); err != nil {
log.Printf("解析答案失败: %v", err)
continue
}
answersMap[ua.QuestionID] = answer
}
// 解析题目ID列表
var questionIDs []uint
if err := json.Unmarshal(exam.QuestionIDs, &questionIDs); err != nil {
log.Printf("解析题目ID失败: %v", err)
return
}
// 查询题目详情
var questions []models.PracticeQuestion
if err := db.Where("id IN ?", questionIDs).Find(&questions).Error; err != nil {
log.Printf("查询题目失败: %v", err)
return
}
// 使用固定的题型分值映射
scoreMap := map[string]float64{
"fill-in-blank": 2.0,
"true-false": 2.0,
"multiple-choice": 1.0,
"multiple-selection": 2.5,
"short-answer": 10.0,
"ordinary-essay": 5.0,
"management-essay": 5.0,
}
// 评分
totalScore := 0.0
aiService, err := services.NewAIGradingService()
if err != nil {
log.Printf("AI服务初始化失败: %v将跳过AI评分", err)
// 不返回错误继续评分流程只是跳过AI评分
}
for _, question := range questions {
userAnswerRaw, answered := answersMap[question.ID]
if !answered {
// 更新数据库中的 ExamUserAnswer 记录为未作答
var userAnswer models.ExamUserAnswer
result := db.Where("exam_record_id = ? AND question_id = ?", recordID, question.ID).First(&userAnswer)
if result.Error == nil {
updates := map[string]interface{}{
"is_correct": false,
"score": 0.0,
}
db.Model(&userAnswer).Updates(updates)
}
continue
}
// 根据题型判断答案
var isCorrect bool
var score float64
var aiGrading *models.AIGrading
switch question.Type {
case "fill-in-blank":
// 填空题:比较数组
userAnswerArr, ok := userAnswerRaw.([]interface{})
if !ok {
isCorrect = false
score = 0
// 更新数据库
var userAnswer models.ExamUserAnswer
if result := db.Where("exam_record_id = ? AND question_id = ?", recordID, question.ID).First(&userAnswer); result.Error == nil {
db.Model(&userAnswer).Updates(map[string]interface{}{
"is_correct": false,
"score": 0.0,
})
}
continue
}
var correctAnswers []string
if err := json.Unmarshal([]byte(question.AnswerData), &correctAnswers); err != nil {
log.Printf("解析填空题答案失败: %v", err)
continue
}
isCorrect = len(userAnswerArr) == len(correctAnswers)
if isCorrect {
for i, ua := range userAnswerArr {
if i >= len(correctAnswers) || fmt.Sprintf("%v", ua) != correctAnswers[i] {
isCorrect = false
break
}
}
}
if isCorrect {
score = scoreMap["fill-in-blank"]
}
case "true-false":
// 判断题 - AnswerData 直接存储 "true" 或 "false" 字符串
correctAnswer := question.AnswerData
isCorrect = fmt.Sprintf("%v", userAnswerRaw) == correctAnswer
if isCorrect {
score = scoreMap["true-false"]
}
case "multiple-choice":
correctAnswer := question.AnswerData
isCorrect = fmt.Sprintf("\"%v\"", userAnswerRaw) == correctAnswer
if isCorrect {
score = scoreMap["multiple-choice"]
}
case "multiple-selection":
// 多选题:比较数组(顺序无关)
userAnswerArr, ok := userAnswerRaw.([]interface{})
if !ok {
isCorrect = false
score = 0
// 更新数据库
var userAnswer models.ExamUserAnswer
if result := db.Where("exam_record_id = ? AND question_id = ?", recordID, question.ID).First(&userAnswer); result.Error == nil {
db.Model(&userAnswer).Updates(map[string]interface{}{
"is_correct": false,
"score": 0.0,
})
}
continue
}
var correctAnswers []string
if err := json.Unmarshal([]byte(question.AnswerData), &correctAnswers); err != nil {
log.Printf("解析多选题答案失败: %v", err)
continue
}
userAnswerSet := make(map[string]bool)
for _, ua := range userAnswerArr {
userAnswerSet[fmt.Sprintf("%v", ua)] = true
}
isCorrect = len(userAnswerSet) == len(correctAnswers)
if isCorrect {
for _, ca := range correctAnswers {
if !userAnswerSet[ca] {
isCorrect = false
break
}
}
}
if isCorrect {
score = scoreMap["multiple-selection"]
}
case "short-answer", "ordinary-essay", "management-essay":
// 简答题和论述题使用AI评分
// AnswerData 直接存储答案文本
correctAnswer := question.AnswerData
userAnswerStr := fmt.Sprintf("%v", userAnswerRaw)
// 检查AI服务是否可用
if aiService == nil {
log.Printf("AI服务不可用无法评分问题 %d", question.ID)
isCorrect = false
score = 0
} else {
aiResult, aiErr := aiService.GradeShortAnswer(question.Question, correctAnswer, userAnswerStr)
if aiErr != nil {
log.Printf("AI评分失败: %v", aiErr)
isCorrect = false
score = 0
} else {
isCorrect = aiResult.IsCorrect
// 按AI评分比例计算
var questionScore float64
if question.Type == "short-answer" {
questionScore = scoreMap["short-answer"]
} else if question.Type == "ordinary-essay" {
questionScore = scoreMap["ordinary-essay"]
} else if question.Type == "management-essay" {
questionScore = scoreMap["management-essay"]
}
score = questionScore * (aiResult.Score / 100.0)
aiGrading = &models.AIGrading{
Score: aiResult.Score,
Feedback: aiResult.Feedback,
Suggestion: aiResult.Suggestion,
}
}
}
}
totalScore += score
// 更新数据库中的 ExamUserAnswer 记录
var userAnswer models.ExamUserAnswer
result := db.Where("exam_record_id = ? AND question_id = ?", recordID, question.ID).First(&userAnswer)
if result.Error == nil {
// 序列化 AI 评分数据
var aiGradingJSON datatypes.JSON
if aiGrading != nil {
aiGradingData, _ := json.Marshal(aiGrading)
aiGradingJSON = datatypes.JSON(aiGradingData)
}
// 更新评分结果
updates := map[string]interface{}{
"is_correct": isCorrect,
"score": score,
"ai_grading_data": aiGradingJSON,
}
db.Model(&userAnswer).Updates(updates)
}
}
// 保存分数和状态到考试记录
record.Score = totalScore
record.Status = "graded"
record.IsPassed = totalScore >= float64(exam.PassScore)
if err := db.Save(&record).Error; err != nil {
log.Printf("保存考试记录失败: %v", err)
return
}
log.Printf("阅卷完成: 考试记录ID=%d, 总分=%.2f, 是否通过=%v", recordID, totalScore, record.IsPassed)
}