270 lines
7.6 KiB
Go
270 lines
7.6 KiB
Go
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)
|
||
}
|