AnCao/internal/handlers/exam_handler.go
yanlongqi 43680cce22 优化试卷打印功能并移除打印答案功能
1. 移除打印答案按钮及相关功能,简化界面
2. 优化填空题打印效果,使用答案长度计算下划线宽度
3. 改进试卷头部布局,添加日期和成绩栏
4. 更新考试说明,调整考试时间为60分钟
5. 优化打印样式,使用宋体字并减小间距
6. 完善论述题显示,添加用户类型提示
7. 后端支持同时返回两种论述题题目

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 02:39:48 +08:00

813 lines
23 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"
"encoding/json"
"fmt"
"log"
"math/rand"
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// CreateExamRequest 创建试卷请求
type CreateExamRequest struct {
Title string `json:"title" binding:"required"`
}
// CreateExam 创建试卷
func CreateExam(c *gin.Context) {
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"success": false, "message": "未登录"})
return
}
var req CreateExamRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的请求数据: " + err.Error()})
return
}
db := database.GetDB()
// 查询用户信息,获取用户类型
var user models.User
if err := db.Where("id = ?", userID).First(&user).Error; err != nil {
log.Printf("查询用户失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "查询用户信息失败"})
return
}
// 使用固定的题型配置总分100分
questionTypes := []models.QuestionTypeConfig{
{Type: "fill-in-blank", Count: 10, Score: 2.0}, // 20分
{Type: "true-false", Count: 10, Score: 2.0}, // 20分
{Type: "multiple-choice", Count: 10, Score: 1.0}, // 10分
{Type: "multiple-selection", Count: 10, Score: 2.5}, // 25分
{Type: "short-answer", Count: 2, Score: 10.0}, // 20分
{Type: "ordinary-essay", Count: 1, Score: 4.5}, // 4.5分(普通涉密人员论述题)
{Type: "management-essay", Count: 1, Score: 4.5}, // 4.5分(保密管理人员论述题)
}
// 按题型配置随机抽取题目
var allQuestionIDs []int64
totalScore := 0.0
for _, qtConfig := range questionTypes {
var questions []models.PracticeQuestion
query := db.Where("type = ?", qtConfig.Type)
if err := query.Find(&questions).Error; err != nil {
log.Printf("查询题目失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "查询题目失败"})
return
}
// 检查题目数量是否足够
if len(questions) < qtConfig.Count {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"message": fmt.Sprintf("题型 %s 题目数量不足,需要 %d 道,实际 %d 道", qtConfig.Type, qtConfig.Count, len(questions)),
})
return
}
// 随机抽取 (Fisher-Yates 洗牌算法)
rand.Seed(time.Now().UnixNano())
for i := len(questions) - 1; i > 0; i-- {
j := rand.Intn(i + 1)
questions[i], questions[j] = questions[j], questions[i]
}
selectedQuestions := questions[:qtConfig.Count]
// 收集题目ID
for _, q := range selectedQuestions {
allQuestionIDs = append(allQuestionIDs, q.ID)
}
// 计算总分
totalScore += float64(qtConfig.Count) * qtConfig.Score
}
// 随机打乱题目ID顺序
rand.Seed(time.Now().UnixNano())
for i := len(allQuestionIDs) - 1; i > 0; i-- {
j := rand.Intn(i + 1)
allQuestionIDs[i], allQuestionIDs[j] = allQuestionIDs[j], allQuestionIDs[i]
}
// 序列化题目ID
questionIDsJSON, err := json.Marshal(allQuestionIDs)
if err != nil {
log.Printf("序列化题目ID失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "生成试卷失败"})
return
}
// 创建试卷
exam := models.Exam{
UserID: uint(userID.(int64)),
Title: req.Title,
TotalScore: int(totalScore), // 总分100分
Duration: 60, // 固定60分钟
PassScore: 80, // 固定80分及格
QuestionIDs: questionIDsJSON,
Status: "active",
}
if err := db.Create(&exam).Error; err != nil {
log.Printf("创建试卷失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "创建试卷失败"})
return
}
// 返回试卷信息
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": gin.H{
"id": exam.ID,
"title": exam.Title,
"total_score": exam.TotalScore,
"duration": exam.Duration,
"pass_score": exam.PassScore,
"question_count": len(allQuestionIDs),
"created_at": exam.CreatedAt,
},
})
}
// GetExamList 获取试卷列表
func GetExamList(c *gin.Context) {
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"success": false, "message": "未登录"})
return
}
db := database.GetDB()
// 查询用户创建的试卷
var exams []models.Exam
if err := db.Where("user_id = ? AND status = ?", userID, "active").
Order("created_at DESC").
Find(&exams).Error; err != nil {
log.Printf("查询试卷列表失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "查询试卷列表失败"})
return
}
// 为每个试卷计算题目数量和获取考试记录统计
type ExamWithStats struct {
models.Exam
QuestionCount int `json:"question_count"`
AttemptCount int `json:"attempt_count"` // 考试次数
BestScore float64 `json:"best_score"` // 最高分
HasInProgressExam bool `json:"has_in_progress_exam"` // 是否有进行中的考试
InProgressRecordID uint `json:"in_progress_record_id,omitempty"` // 进行中的考试记录ID
}
result := make([]ExamWithStats, 0, len(exams))
for _, exam := range exams {
var questionIDs []uint
if err := json.Unmarshal(exam.QuestionIDs, &questionIDs); err == nil {
stats := ExamWithStats{
Exam: exam,
QuestionCount: len(questionIDs),
}
// 查询该试卷的考试记录统计
var count int64
db.Model(&models.ExamRecord{}).Where("exam_id = ? AND user_id = ?", exam.ID, userID).Count(&count)
stats.AttemptCount = int(count)
// 查询最高分
var record models.ExamRecord
if err := db.Where("exam_id = ? AND user_id = ?", exam.ID, userID).
Order("score DESC").
First(&record).Error; err == nil {
stats.BestScore = record.Score
}
// 查询是否有进行中的考试status为in_progress
var inProgressRecord models.ExamRecord
if err := db.Where("exam_id = ? AND user_id = ? AND status = ?", exam.ID, userID, "in_progress").
Order("created_at DESC").
First(&inProgressRecord).Error; err == nil {
stats.HasInProgressExam = true
stats.InProgressRecordID = inProgressRecord.ID
}
result = append(result, stats)
}
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": result,
})
}
// GetExamDetail 获取试卷详情
func GetExamDetail(c *gin.Context) {
_, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"success": false, "message": "未登录"})
return
}
examIDStr := c.Param("id")
examID, err := strconv.ParseUint(examIDStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的试卷ID"})
return
}
// 检查是否需要显示答案
showAnswer := c.Query("show_answer") == "true"
db := database.GetDB()
// 查询试卷
var exam models.Exam
if err := db.Where("id = ?", examID).First(&exam).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"success": false, "message": "试卷不存在"})
return
}
// 解析题目ID列表
var questionIDs []int64
if err := json.Unmarshal(exam.QuestionIDs, &questionIDs); err != nil {
log.Printf("解析题目ID失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "解析试卷数据失败"})
return
}
// 查询题目详情
var questions []models.PracticeQuestion
if err := db.Where("id IN ?", questionIDs).Find(&questions).Error; err != nil {
log.Printf("查询题目失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "查询题目失败"})
return
}
// 按原始顺序排序题目并转换为DTO
questionMap := make(map[int64]models.PracticeQuestion)
for _, q := range questions {
questionMap[q.ID] = q
}
// 检查是否包含论述题,如果没有则添加两种论述题
hasOrdinaryEssay := false
hasManagementEssay := false
for _, q := range questions {
if q.Type == "ordinary-essay" {
hasOrdinaryEssay = true
}
if q.Type == "management-essay" {
hasManagementEssay = true
}
}
// 如果缺少论述题,则补充
var additionalQuestions []models.PracticeQuestion
if !hasOrdinaryEssay {
var ordinaryEssay models.PracticeQuestion
if err := db.Where("type = ?", "ordinary-essay").First(&ordinaryEssay).Error; err == nil {
additionalQuestions = append(additionalQuestions, ordinaryEssay)
}
}
if !hasManagementEssay {
var managementEssay models.PracticeQuestion
if err := db.Where("type = ?", "management-essay").First(&managementEssay).Error; err == nil {
additionalQuestions = append(additionalQuestions, managementEssay)
}
}
// 将补充的题目添加到题目映射中
for _, q := range additionalQuestions {
questionMap[q.ID] = q
}
orderedDTOs := make([]models.PracticeQuestionDTO, 0, len(questionIDs))
for _, id := range questionIDs {
if q, ok := questionMap[id]; ok {
dto := convertToDTO(q)
// 根据showAnswer参数决定是否显示答案
if !showAnswer {
dto.Answer = nil // 不显示答案
}
orderedDTOs = append(orderedDTOs, dto)
}
}
// 添加补充的论述题到结果中
for _, q := range additionalQuestions {
dto := convertToDTO(q)
if !showAnswer {
dto.Answer = nil // 不显示答案
}
orderedDTOs = append(orderedDTOs, dto)
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": gin.H{
"exam": exam,
"questions": orderedDTOs,
},
})
}
// StartExam 开始考试(创建考试记录)
func StartExam(c *gin.Context) {
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"success": false, "message": "未登录"})
return
}
examIDStr := c.Param("id")
examID, err := strconv.ParseUint(examIDStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的试卷ID"})
return
}
db := database.GetDB()
// 查询试卷
var exam models.Exam
if err := db.Where("id = ?", examID).First(&exam).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"success": false, "message": "试卷不存在"})
return
}
// 创建考试记录
now := time.Now()
record := models.ExamRecord{
ExamID: uint(examID),
UserID: uint(userID.(int64)),
StartTime: &now,
TotalScore: exam.TotalScore,
Status: "in_progress",
}
if err := db.Create(&record).Error; err != nil {
log.Printf("创建考试记录失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "开始考试失败"})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": gin.H{
"record_id": record.ID,
"start_time": record.StartTime,
"duration": exam.Duration,
},
})
}
// SubmitExam 提交试卷答案
func SubmitExam(c *gin.Context) {
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"success": false, "message": "未登录"})
return
}
recordIDStr := c.Param("record_id")
recordID, err := strconv.ParseUint(recordIDStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的考试记录ID"})
return
}
// 解析请求体
var req struct {
Answers map[string]interface{} `json:"answers"` // question_id -> answer (可选)
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的请求数据"})
return
}
db := database.GetDB()
// 查询考试记录
var record models.ExamRecord
if err := db.Where("id = ? AND user_id = ?", recordID, userID).First(&record).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"success": false, "message": "考试记录不存在"})
return
}
// 检查是否已提交
if record.Status == "submitted" || record.Status == "graded" {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "考试已提交"})
return
}
// 查询试卷
var exam models.Exam
if err := db.Where("id = ?", record.ExamID).First(&exam).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"success": false, "message": "试卷不存在"})
return
}
// 更新考试记录状态为已提交
now := time.Now()
record.Status = "submitted"
record.SubmitTime = &now
// 计算用时(秒)
if record.StartTime != nil {
duration := now.Sub(*record.StartTime)
record.TimeSpent = int(duration.Seconds())
// 确保用时不为负数(容错处理)
if record.TimeSpent < 0 {
log.Printf("警告: 计算出负的用时,开始时间=%v, 结束时间=%v", *record.StartTime, now)
record.TimeSpent = 0
}
}
if err := db.Save(&record).Error; err != nil {
log.Printf("保存考试记录失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "提交考试失败"})
return
}
// 立即返回成功响应
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "提交成功,正在阅卷中...",
"data": gin.H{
"record_id": record.ID,
"status": "submitted",
"time_spent": record.TimeSpent,
"total_score": exam.TotalScore,
},
})
// 异步执行阅卷(从 exam_user_answers 表读取答案)
go gradeExam(uint(recordID), exam.ID, uint(userID.(int64)))
}
// GetExamRecord 获取考试记录详情
func GetExamRecord(c *gin.Context) {
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"success": false, "message": "未登录"})
return
}
recordIDStr := c.Param("record_id")
recordID, err := strconv.ParseUint(recordIDStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的考试记录ID"})
return
}
db := database.GetDB()
// 查询考试记录
var record models.ExamRecord
if err := db.Where("id = ? AND user_id = ?", recordID, userID).
Preload("Exam").
First(&record).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"success": false, "message": "考试记录不存在"})
return
}
// 从 exam_user_answers 表读取所有答案
var userAnswers []models.ExamUserAnswer
if err := db.Where("exam_record_id = ?", recordID).Find(&userAnswers).Error; err != nil {
log.Printf("查询用户答案失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "查询答案失败"})
return
}
// 查询所有题目以获取正确答案
var questionIDs []uint
if err := json.Unmarshal(record.Exam.QuestionIDs, &questionIDs); err != nil {
log.Printf("解析题目ID失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "解析题目失败"})
return
}
var questions []models.PracticeQuestion
if err := db.Where("id IN ?", questionIDs).Find(&questions).Error; err != nil {
log.Printf("查询题目失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "查询题目失败"})
return
}
// 构建题目映射
questionMap := make(map[int64]models.PracticeQuestion)
for _, q := range questions {
questionMap[q.ID] = q
}
// 构建 ExamAnswer 列表
examAnswers := make([]models.ExamAnswer, 0, len(userAnswers))
for _, ua := range userAnswers {
// 解析用户答案
var userAnswer interface{}
if err := json.Unmarshal(ua.Answer, &userAnswer); err != nil {
log.Printf("解析用户答案失败: %v", err)
continue
}
// 获取题目并解析正确答案
question, ok := questionMap[ua.QuestionID]
if !ok {
continue
}
var correctAnswerRaw interface{}
switch question.Type {
case "fill-in-blank", "multiple-selection", "multiple-choice":
// 数组类型:需要 JSON 解析单选题也是数组格式A
var arr []string
if err := json.Unmarshal([]byte(question.AnswerData), &arr); err != nil {
// 尝试解析为单个字符串(兼容旧数据格式)
var singleStr string
if err2 := json.Unmarshal([]byte(question.AnswerData), &singleStr); err2 == nil {
// 成功解析为字符串,单选题直接使用,其他类型转为数组
if question.Type == "multiple-choice" {
correctAnswerRaw = singleStr
} else {
correctAnswerRaw = []string{singleStr}
}
} else {
correctAnswerRaw = "解析失败"
}
} else {
// 单选题只取第一个元素
if question.Type == "multiple-choice" && len(arr) > 0 {
correctAnswerRaw = arr[0]
} else {
correctAnswerRaw = arr
}
}
default:
// 字符串类型:直接使用 AnswerDatatrue-false, short-answer, essay
correctAnswerRaw = question.AnswerData
}
// 构建答案对象
examAnswer := models.ExamAnswer{
QuestionID: ua.QuestionID,
Answer: userAnswer,
CorrectAnswer: correctAnswerRaw,
IsCorrect: ua.IsCorrect != nil && *ua.IsCorrect,
Score: ua.Score,
}
// 添加 AI 评分信息
if len(ua.AIGradingData) > 0 {
var aiGrading models.AIGrading
if err := json.Unmarshal(ua.AIGradingData, &aiGrading); err == nil {
examAnswer.AIGrading = &aiGrading
}
}
examAnswers = append(examAnswers, examAnswer)
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": gin.H{
"record": record,
"answers": examAnswers,
},
})
}
// GetExamRecordList 获取考试记录列表
func GetExamRecordList(c *gin.Context) {
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"success": false, "message": "未登录"})
return
}
examIDStr := c.Query("exam_id")
db := database.GetDB()
query := db.Where("user_id = ?", userID)
// 如果指定了试卷ID,只查询该试卷的记录
if examIDStr != "" {
examID, err := strconv.ParseUint(examIDStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的试卷ID"})
return
}
query = query.Where("exam_id = ?", examID)
}
var records []models.ExamRecord
if err := query.Preload("Exam").
Order("created_at DESC").
Find(&records).Error; err != nil {
log.Printf("查询考试记录失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "查询考试记录失败"})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": records,
})
}
// DeleteExam 删除试卷
func DeleteExam(c *gin.Context) {
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"success": false, "message": "未登录"})
return
}
examIDStr := c.Param("id")
examID, err := strconv.ParseUint(examIDStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的试卷ID"})
return
}
db := database.GetDB()
// 查询试卷
var exam models.Exam
if err := db.Where("id = ? AND user_id = ?", examID, userID).First(&exam).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"success": false, "message": "试卷不存在"})
return
}
// 软删除
if err := db.Delete(&exam).Error; err != nil {
log.Printf("删除试卷失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "删除试卷失败"})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "删除成功",
})
}
// SaveExamProgressRequest 保存考试进度请求
type SaveExamProgressRequest struct {
QuestionID int64 `json:"question_id"` // 题目ID
Answer interface{} `json:"answer"` // 答案数据
}
// SaveExamProgress 保存单题答案
func SaveExamProgress(c *gin.Context) {
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"success": false, "message": "未登录"})
return
}
recordIDStr := c.Param("record_id")
recordID, err := strconv.ParseInt(recordIDStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的考试记录ID"})
return
}
var req SaveExamProgressRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的请求数据"})
return
}
db := database.GetDB()
// 查询考试记录
var record models.ExamRecord
if err := db.Where("id = ? AND user_id = ?", recordID, userID).First(&record).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"success": false, "message": "考试记录不存在"})
return
}
// 检查考试状态
if record.Status != "in_progress" {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "考试已结束,无法保存答案"})
return
}
// 序列化答案数据
answerJSON, err := json.Marshal(req.Answer)
if err != nil {
log.Printf("序列化答案失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "保存失败"})
return
}
now := time.Now()
// 查找是否已存在该题的答案
var userAnswer models.ExamUserAnswer
result := db.Where("exam_record_id = ? AND question_id = ?", recordID, req.QuestionID).First(&userAnswer)
if result.Error == gorm.ErrRecordNotFound {
// 不存在,创建新记录
userAnswer = models.ExamUserAnswer{
ExamRecordID: recordID,
QuestionID: req.QuestionID,
UserID: userID.(int64),
Answer: answerJSON,
AnsweredAt: &now,
LastModifiedAt: now,
}
if err := db.Create(&userAnswer).Error; err != nil {
log.Printf("创建答案记录失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "保存答案失败"})
return
}
} else if result.Error != nil {
log.Printf("查询答案记录失败: %v", result.Error)
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "查询答案失败"})
return
} else {
// 已存在,更新答案
updates := map[string]interface{}{
"answer": answerJSON,
"last_modified_at": now,
}
if err := db.Model(&userAnswer).Updates(updates).Error; err != nil {
log.Printf("更新答案记录失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "更新答案失败"})
return
}
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "保存成功",
})
}
// GetExamUserAnswers 获取用户在考试中的所有答案
func GetExamUserAnswers(c *gin.Context) {
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"success": false, "message": "未登录"})
return
}
recordIDStr := c.Param("record_id")
recordID, err := strconv.ParseUint(recordIDStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的考试记录ID"})
return
}
db := database.GetDB()
// 查询考试记录,确保用户有权限
var record models.ExamRecord
if err := db.Where("id = ? AND user_id = ?", recordID, userID).First(&record).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"success": false, "message": "考试记录不存在"})
return
}
// 查询所有已保存的答案
var userAnswers []models.ExamUserAnswer
if err := db.Where("exam_record_id = ?", recordID).Find(&userAnswers).Error; err != nil {
log.Printf("查询用户答案失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "查询答案失败"})
return
}
// 转换为前端需要的格式: { question_id: answer }
answers := make(map[string]interface{})
for _, ua := range userAnswers {
var answer interface{}
if err := json.Unmarshal(ua.Answer, &answer); err != nil {
log.Printf("解析答案失败: %v", err)
continue
}
// 使用 q_<question_id> 格式作为key与前端表单字段名保持一致
fieldName := fmt.Sprintf("q_%d", ua.QuestionID)
answers[fieldName] = answer
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": answers,
})
}