Files
TechHelper/TechHelper.Server/Services/ExamService.cs
SpecialX e824c081bf change
2025-05-30 12:46:55 +08:00

307 lines
9.7 KiB
C#
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.

using AutoMapper;
using Entities.Contracts;
using Entities.DTO;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using SharedDATA.Api;
using TechHelper.Services;
namespace TechHelper.Server.Services
{
public class ExamService : IExamService
{
private readonly IUnitOfWork _unitOfWork;
private readonly IRepository<Assignment> _assignmentRepo;
private readonly IRepository<AssignmentGroup> _assignmentGroupRepo;
private readonly IRepository<AssignmentQuestion> _assignmentQuestionRepo;
private readonly IRepository<Question> _questionRepo;
public ExamService(IUnitOfWork unitOfWork, IMapper mapper, UserManager<User> userManager)
{
_unitOfWork = unitOfWork;
_mapper = mapper;
_userManager = userManager;
_assignmentRepo = _unitOfWork.GetRepository<Assignment>();
_assignmentGroupRepo = _unitOfWork.GetRepository<AssignmentGroup>();
_assignmentQuestionRepo = _unitOfWork.GetRepository<AssignmentQuestion>();
_questionRepo = _unitOfWork.GetRepository<Question>();
}
private readonly IMapper _mapper;
private readonly UserManager<User> _userManager;
public async Task<ApiResponse> AddAsync(ExamDto model)
{
try
{
var result = await SaveParsedExam(model);
if (result.Status)
{
return ApiResponse.Success("保存试题成功");
}
else
{
return ApiResponse.Error($"保存试题数据失败{result.Message}");
}
}
catch (Exception ex)
{
return ApiResponse.Error($"保存试题数据失败: {ex.Message}");
}
}
public Task<ApiResponse> DeleteAsync(Guid id)
{
throw new NotImplementedException();
}
public Task<ApiResponse> GetAllAsync(QueryParameter query)
{
throw new NotImplementedException();
}
public async Task<ApiResponse> GetAsync(Guid id)
{
try
{
var result = await GetExamByIdAsync(id);
return result;
}
catch (Exception ex)
{
return ApiResponse.Error($"获取试题数据失败: {ex.Message}");
}
}
public Task<ApiResponse> UpdateAsync(ExamDto model)
{
throw new NotImplementedException();
}
public async Task<ApiResponse> GetExamByIdAsync(Guid assignmentId)
{
try
{
var assignment = await _unitOfWork.GetRepository<Assignment>().GetFirstOrDefaultAsync(
predicate: a => a.Id == assignmentId && !a.IsDeleted);
if (assignment == null)
{
return ApiResponse.Error($"找不到 ID 为 {assignmentId} 的试卷。");
}
// 获取所有相关题组和题目,并过滤掉已删除的
var allGroups = await _unitOfWork.GetRepository<AssignmentGroup>().GetAllAsync(
predicate: ag => ag.AssignmentId == assignmentId && !ag.IsDeleted,
include: source => source
.Include(ag => ag.AssignmentQuestions.Where(aq => !aq.IsDeleted))
.ThenInclude(aq => aq.Question)
);
if (allGroups == null || !allGroups.Any())
{
// 试卷存在但没有内容,返回一个空的 ExamDto
return ApiResponse.Success("试卷没有内容。", new ExamDto
{
AssignmentId = assignment.Id,
AssignmentTitle = assignment.Title,
Description = assignment.Description,
SubjectArea = assignment.Submissions.ToString()
});
}
var rootGroups = allGroups
.Where(ag => ag.ParentGroup == null)
.OrderBy(ag => ag.Number)
.ToList();
// 递归映射到 ExamDto
var examDto = new ExamDto
{
AssignmentId = assignment.Id,
AssignmentTitle = assignment.Title,
Description = assignment.Description,
SubjectArea = assignment.Submissions.ToString(),
QuestionGroups = MapAssignmentGroupsToDto(rootGroups, allGroups)
};
return ApiResponse.Success("试卷信息已成功获取。", examDto);
}
catch (Exception ex)
{
return ApiResponse.Error($"获取试卷时发生错误: {ex.Message}", ex);
}
}
private List<QuestionGroupDto> MapAssignmentGroupsToDto(
List<AssignmentGroup> currentLevelGroups,
IEnumerable<AssignmentGroup> allFetchedGroups)
{
var dtos = new List<QuestionGroupDto>();
foreach (var group in currentLevelGroups.OrderBy(g => g.Number))
{
var groupDto = new QuestionGroupDto
{
Title = group.Title,
Score = (int)(group.TotalPoints ?? 0),
QuestionReference = group.Descript,
SubQuestions = group.AssignmentQuestions
.OrderBy(aq => aq.QuestionNumber)
.Select(aq => new SubQuestionDto
{
Index = aq.QuestionNumber,
Stem = aq.Question.QuestionText,
Score = aq.Score?? 0, // 使用 AssignmentQuestion 上的 Score
SampleAnswer = aq.Question.CorrectAnswer,
QuestionType = aq.Question.QuestionType.ToString(),
DifficultyLevel = aq.Question.DifficultyLevel.ToString(),
Options = new List<OptionDto>() // 这里需要您根据实际存储方式填充 Option
}).ToList(),
// 递归映射子题组
SubQuestionGroups = MapAssignmentGroupsToDto(
allFetchedGroups.Where(ag => ag.ParentGroup == group.Id && !ag.IsDeleted).ToList(), // 从所有已获取的组中筛选子组
allFetchedGroups)
};
dtos.Add(groupDto);
}
return dtos;
}
public async Task<TechHelper.Services.ApiResponse> SaveParsedExam(ExamDto examData)
{
// 获取当前登录用户
var currentUser = await _userManager.GetUserAsync(null);
if (currentUser == null)
{
return ApiResponse.Error("未找到当前登录用户,无法保存试题。");
}
var currentUserId = currentUser.Id;
try
{
Guid assignmentId;
// 创建新的 Assignment 实体
var newAssignment = new Assignment
{
Id = Guid.NewGuid(),
Title = examData.AssignmentTitle,
Description = examData.Description,
SubjectArea = examData.SubjectArea,
CreatedAt = DateTime.UtcNow,
CreatedBy = currentUserId,
IsDeleted = false
};
await _assignmentRepo.InsertAsync(newAssignment);
assignmentId = newAssignment.Id;
// 从 ExamDto.QuestionGroups 获取根题组。
// 确保只有一个根题组,因为您的模型是“试卷只有一个根节点”。
if (examData.QuestionGroups == null || examData.QuestionGroups.Count != 1)
{
throw new ArgumentException("试卷必须包含且只能包含一个根题组。");
}
// 递归处理根题组及其所有子题组和题目
// 传入的 assignmentId 仅用于设置根题组的 AssignmentId 字段
// 对于子题组ProcessAndSaveAssignmentGroupsRecursive 会将 AssignmentId 设置为 null
await ProcessAndSaveAssignmentGroupsRecursive(
examData.QuestionGroups.Single(),
examData.SubjectArea.ToString(),
assignmentId,
null, // 根题组没有父级
currentUserId);
if (await _unitOfWork.SaveChangesAsync() > 0)
{
return ApiResponse.Success("试卷数据已成功保存。", new ExamDto { AssignmentId = assignmentId, AssignmentTitle = examData.AssignmentTitle });
}
else
{
return ApiResponse.Success("没有新的试卷数据需要保存。");
}
}
catch (Exception ex)
{
return ApiResponse.Error($"保存试卷数据失败: {ex.Message}", ex);
}
}
private async Task ProcessAndSaveAssignmentGroupsRecursive(
QuestionGroupDto qgDto,
string subjectarea,
Guid assignmentId,
Guid? parentAssignmentGroupId,
Guid createdById)
{
byte groupNumber = 1;
var newAssignmentGroup = new AssignmentGroup
{
Id = Guid.NewGuid(), // 后端生成 GUID
Title = qgDto.Title,
Descript = qgDto.QuestionReference,
TotalPoints = qgDto.Score,
Number = (byte)qgDto.Index, // 使用 DTO 的 Index 作为 Number
ParentGroup = parentAssignmentGroupId, // 设置父级题组 GUID
// 关键修正:只有当 parentAssignmentGroupId 为 null 时,才设置 AssignmentId
// 这意味着当前题组是顶级题组
AssignmentId = parentAssignmentGroupId == null ? assignmentId : Guid.Empty,
IsDeleted = false
};
await _unitOfWork.GetRepository<AssignmentGroup>().InsertAsync(newAssignmentGroup);
// 处理子题目
uint questionNumber = 1;
foreach (var sqDto in qgDto.SubQuestions.OrderBy(s => s.Index))
{
var newQuestion = _mapper.Map<Question>(sqDto);
newQuestion.Id = Guid.NewGuid();
newQuestion.CreatedBy = createdById;
newQuestion.CreatedAt = DateTime.UtcNow;
newQuestion.UpdatedAt = DateTime.UtcNow;
newQuestion.IsDeleted = false;
newQuestion.SubjectArea = (SubjectAreaEnum)Enum.Parse(typeof(SubjectAreaEnum), subjectarea, true);
// 处理 Options如果 Options 是 JSON 字符串或需要其他存储方式,在这里处理
// 例如newQuestion.QuestionText += (JsonConvert.SerializeObject(sqDto.Options));
await _unitOfWork.GetRepository<Question>().InsertAsync(newQuestion);
var newAssignmentQuestion = new AssignmentQuestion
{
Id = Guid.NewGuid(),
QuestionId = newQuestion.Id,
QuestionNumber = (byte)questionNumber, // 使用递增的 questionNumber
AssignmentGroupId = newAssignmentGroup.Id, // 关联到当前题组
Score = sqDto.Score, // 从 DTO 获取单个子题分数
IsDeleted = false,
CreatedAt = DateTime.UtcNow
};
await _unitOfWork.GetRepository<AssignmentQuestion>().InsertAsync(newAssignmentQuestion);
questionNumber++;
}
// 递归处理子题组
// 这里需要遍历 SubQuestionGroups并对每个子组进行递归调用
foreach (var subQgDto in qgDto.SubQuestionGroups.OrderBy(s => s.Index))
{
await ProcessAndSaveAssignmentGroupsRecursive(
subQgDto, // 传入当前的子题组 DTO
subjectarea,
assignmentId, // 顶层 AssignmentId 依然传递下去,但子组不会直接使用它
newAssignmentGroup.Id, // 将当前题组的 ID 作为下一层递归的 parentAssignmentGroupId
createdById);
}
}
}
}