using AutoMapper; using Entities.Contracts; using Entities.DTO; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using MySqlConnector; using SharedDATA.Api; using TechHelper.Services; using static TechHelper.Context.AutoMapperProFile; namespace TechHelper.Server.Services { public class ExamService : IExamService { private readonly IUnitOfWork _unitOfWork; private readonly IRepository _assignmentRepo; private readonly IRepository _assignmentGroupRepo; private readonly IRepository _assignmentQuestionRepo; private readonly IRepository _questionRepo; public ExamService(IUnitOfWork unitOfWork, IMapper mapper, UserManager userManager) { _unitOfWork = unitOfWork; _mapper = mapper; _userManager = userManager; _assignmentRepo = _unitOfWork.GetRepository(); _assignmentGroupRepo = _unitOfWork.GetRepository(); _assignmentQuestionRepo = _unitOfWork.GetRepository(); _questionRepo = _unitOfWork.GetRepository(); } private readonly IMapper _mapper; private readonly UserManager _userManager; public async Task 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 DeleteAsync(Guid id) { throw new NotImplementedException(); } public Task GetAllAsync(QueryParameter query) { throw new NotImplementedException(); } public async Task GetAsync(Guid id) { try { var result = await GetExamByIdAsync(id); return result; } catch (Exception ex) { return ApiResponse.Error($"获取试题数据失败: {ex.Message}"); } } public Task UpdateAsync(ExamDto model) { throw new NotImplementedException(); } public async Task> LoadFullGroupTree(Guid rootGroupId) { var query = @" WITH RECURSIVE GroupTree AS ( SELECT ag.*, CAST(ag.`number` AS CHAR(255)) AS path FROM assignment_group ag WHERE ag.id = @rootId UNION ALL SELECT c.*, CONCAT(ct.path, '.', c.`number`) FROM assignment_group c INNER JOIN GroupTree ct ON c.parent_group = ct.id ) SELECT * FROM GroupTree ORDER BY path; "; // 执行查询 var groups = await _unitOfWork.GetRepository() .FromSql(query, new MySqlParameter("rootId", rootGroupId)) .ToListAsync(); // 内存中构建树结构 var groupDict = groups.ToDictionary(g => g.Id); var root = groupDict[rootGroupId]; foreach (var group in groups) { if (group.ParentGroup != null && groupDict.TryGetValue(group.ParentGroup.Value, out var parent)) { parent.ChildAssignmentGroups ??= new List(); parent.ChildAssignmentGroups.Add(group); } } return new List { root }; } public async Task LoadRecursiveAssignmentGroups(IEnumerable groups) { foreach (var group in groups.ToList()) { var loadedGroup = await _unitOfWork.GetRepository() .GetFirstOrDefaultAsync( predicate: ag => ag.Id == group.Id, include: source => source .Include(ag => ag.AssignmentQuestions.Where(aq => !aq.IsDeleted)) .ThenInclude(aq => aq.Question) .Include(ag => ag.ChildAssignmentGroups) ); if (loadedGroup == null) continue; group.ChildAssignmentGroups = loadedGroup.ChildAssignmentGroups; group.AssignmentQuestions = loadedGroup.AssignmentQuestions; if (group.ChildAssignmentGroups is { Count: > 0 }) { await LoadRecursiveAssignmentGroups(group.ChildAssignmentGroups); } } } public async Task GetExamByIdAsync(Guid assignmentId) { try { var assignment = await _unitOfWork.GetRepository().GetFirstOrDefaultAsync( predicate: a => a.Id == assignmentId && !a.IsDeleted); if (assignment == null) { return ApiResponse.Error($"找不到 ID 为 {assignmentId} 的试卷。"); } // 获取所有相关题组和题目,并过滤掉已删除的 var allGroups = await _unitOfWork.GetRepository().GetFirstOrDefaultAsync( predicate: ag => ag.AssignmentId == assignmentId && !ag.IsDeleted, include: source => source .Include(ag => ag.ChildAssignmentGroups) ); await LoadRecursiveAssignmentGroups(allGroups.ChildAssignmentGroups); if (allGroups == null || !allGroups.ChildAssignmentGroups.Any()) { // 试卷存在但没有内容,返回一个空的 ExamDto return ApiResponse.Success("试卷没有内容。", new ExamDto { AssignmentId = assignment.Id, AssignmentTitle = assignment.Title, Description = assignment.Description, SubjectArea = assignment.Submissions.ToString() }); } var rootGroups = allGroups.ChildAssignmentGroups.ToList(); var rootqg = new QuestionGroupDto(); foreach (var ag in rootGroups.OrderBy(g => g.Number)) { var agDto = MapAssignmentGroupToDto(ag); rootqg.SubQuestionGroups.Add(agDto); } // 递归映射到 ExamDto var examDto = new ExamDto { AssignmentId = assignment.Id, AssignmentTitle = assignment.Title, Description = assignment.Description, SubjectArea = assignment.Submissions.ToString(), QuestionGroups = rootqg }; return ApiResponse.Success("试卷信息已成功获取。", examDto); } catch (Exception ex) { return ApiResponse.Error($"获取试卷时发生错误: {ex.Message}", ex); } } public QuestionGroupDto MapAssignmentGroupToDto(AssignmentGroup ag) { // 创建当前节点的DTO var dto = new QuestionGroupDto { Title = ag.Title, Score = (int)(ag.TotalPoints ?? 0), Descript = ag.Descript, SubQuestions = ag.AssignmentQuestions? .OrderBy(aq => aq.QuestionNumber) .Select(aq => new SubQuestionDto { Index = aq.QuestionNumber, Stem = aq.Question?.QuestionText, Score = aq.Score ?? 0, SampleAnswer = aq.Question?.CorrectAnswer, QuestionType = aq.Question?.QuestionType.ToString(), DifficultyLevel = aq.Question?.DifficultyLevel.ToString(), Options = new List() // 根据需要初始化 }).ToList() ?? new List(), SubQuestionGroups = new List() // 初始化子集合 }; // 递归处理子组 if (ag.ChildAssignmentGroups != null && ag.ChildAssignmentGroups.Count > 0) { foreach (var child in ag.ChildAssignmentGroups.OrderBy(c => c.Number)) { var childDto = MapAssignmentGroupToDto(child); // 递归获取子DTO dto.SubQuestionGroups.Add(childDto); // 添加到当前节点的子集合 } } return dto; } private List MapAssignmentGroupsToDto2( List currentLevelGroups, IEnumerable allFetchedGroups) { var dtos = new List(); foreach (var group in currentLevelGroups.OrderBy(g => g.Number)) { var groupDto = new QuestionGroupDto { Title = group.Title, Score = (int)(group.TotalPoints ?? 0), Descript = group.Descript, SubQuestions = group.AssignmentQuestions .OrderBy(aq => aq.QuestionNumber) .Select(aq => new SubQuestionDto { Index = aq.QuestionNumber, Stem = aq.Question.QuestionText, Score = aq.Score ?? 0, SampleAnswer = aq.Question.CorrectAnswer, QuestionType = aq.Question.QuestionType.ToString(), DifficultyLevel = aq.Question.DifficultyLevel.ToString(), Options = new List() }).ToList(), // 递归映射子题组 SubQuestionGroups = MapAssignmentGroupsToDto2( allFetchedGroups.Where(ag => ag.ParentGroup == group.Id && !ag.IsDeleted).ToList(), allFetchedGroups) }; dtos.Add(groupDto); } return dtos; } public async Task SaveParsedExam(ExamDto examData) { // 获取当前登录用户 var currentUser = await _userManager.FindByEmailAsync(examData.CreaterEmail); 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) { throw new ArgumentException("试卷必须包含且只能包含一个根题组。"); } await ProcessAndSaveAssignmentGroupsRecursive( examData.QuestionGroups, 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.Descript, TotalPoints = qgDto.Score, Number = (byte)qgDto.Index, ValidQuestionGroup = qgDto.ValidQuestionGroup, ParentGroup = parentAssignmentGroupId, AssignmentId = parentAssignmentGroupId == null ? assignmentId : (Guid?)null, IsDeleted = false }; await _unitOfWork.GetRepository().InsertAsync(newAssignmentGroup); // 处理子题目 uint questionNumber = 1; foreach (var sqDto in qgDto.SubQuestions.OrderBy(s => s.Index)) { var newQuestion = _mapper.Map(sqDto); newQuestion.Id = Guid.NewGuid(); newQuestion.CreatedBy = createdById; newQuestion.CreatedAt = DateTime.UtcNow; newQuestion.UpdatedAt = DateTime.UtcNow; newQuestion.IsDeleted = false; newQuestion.SubjectArea = EnumMappingHelpers.ParseEnumSafe(subjectarea, SubjectAreaEnum.Unknown); await _unitOfWork.GetRepository().InsertAsync(newQuestion); var newAssignmentQuestion = new AssignmentQuestion { Id = Guid.NewGuid(), QuestionId = newQuestion.Id, QuestionNumber = (byte)questionNumber, AssignmentGroupId = newAssignmentGroup.Id, Score = sqDto.Score, IsDeleted = false, CreatedAt = DateTime.UtcNow }; await _unitOfWork.GetRepository().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); } } public async Task GetAllExamPreview(Guid user) { try { var assignments = await _unitOfWork.GetRepository().GetAllAsync( predicate: a => a.CreatedBy == user && !a.IsDeleted); if (assignments.Any()) { var exam = _mapper.Map>(assignments); return ApiResponse.Success(result: exam); } return ApiResponse.Error("你还没有创建任何试卷"); } catch (Exception ex) { return ApiResponse.Error($"查询出了一点问题 , 详细信息为: {ex.Message}, 请稍后再试"); } } } }