434 lines
13 KiB
C#
434 lines
13 KiB
C#
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<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<IEnumerable<AssignmentGroup>> 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<AssignmentGroup>()
|
||
.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<AssignmentGroup>();
|
||
parent.ChildAssignmentGroups.Add(group);
|
||
}
|
||
}
|
||
|
||
return new List<AssignmentGroup> { root };
|
||
}
|
||
|
||
public async Task LoadRecursiveAssignmentGroups(IEnumerable<AssignmentGroup> groups)
|
||
{
|
||
foreach (var group in groups.ToList())
|
||
{
|
||
|
||
var loadedGroup = await _unitOfWork.GetRepository<AssignmentGroup>()
|
||
.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<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>().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<OptionDto>() // 根据需要初始化
|
||
}).ToList() ?? new List<SubQuestionDto>(),
|
||
SubQuestionGroups = new List<QuestionGroupDto>() // 初始化子集合
|
||
};
|
||
|
||
// 递归处理子组
|
||
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<QuestionGroupDto> MapAssignmentGroupsToDto2(
|
||
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),
|
||
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<OptionDto>()
|
||
}).ToList(),
|
||
// 递归映射子题组
|
||
SubQuestionGroups = MapAssignmentGroupsToDto2(
|
||
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.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<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 = EnumMappingHelpers.ParseEnumSafe(subjectarea, SubjectAreaEnum.Unknown);
|
||
|
||
await _unitOfWork.GetRepository<Question>().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<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);
|
||
}
|
||
}
|
||
|
||
public async Task<ApiResponse> GetAllExamPreview(Guid user)
|
||
{
|
||
try
|
||
{
|
||
var assignments = await _unitOfWork.GetRepository<Assignment>().GetAllAsync(
|
||
predicate: a => a.CreatedBy == user && !a.IsDeleted);
|
||
|
||
if (assignments.Any())
|
||
{
|
||
var exam = _mapper.Map<IEnumerable<ExamDto>>(assignments);
|
||
return ApiResponse.Success(result: exam);
|
||
}
|
||
|
||
return ApiResponse.Error("你还没有创建任何试卷");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
return ApiResponse.Error($"查询出了一点问题 , 详细信息为: {ex.Message}, 请稍后再试");
|
||
}
|
||
}
|
||
}
|
||
}
|