struct&&assiQues

This commit is contained in:
SpecialX
2025-06-20 15:37:39 +08:00
parent f37262d72e
commit d20c051c51
68 changed files with 1927 additions and 2869 deletions

View File

@@ -1,56 +1,149 @@
using AutoMapper;
using Entities.Contracts;
using Entities.DTO;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using MySqlConnector;
using Microsoft.VisualBasic;
using SharedDATA.Api;
using TechHelper.Context;
using TechHelper.Server.Repositories;
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)
private readonly IExamRepository _examRepository;
private readonly IMapper _mapper;
public ExamService(IUnitOfWork unitOfWork, IExamRepository examRepository, IMapper mapper)
{
_unitOfWork = unitOfWork;
_examRepository = examRepository;
_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)
public async Task<ApiResponse> CreateExamAsync(AssignmentDto assignmentDto)
{
try
Assignment newAssi = _mapper.Map<Assignment>(assignmentDto);
await _examRepository.AddAsync(newAssi);
var context = _unitOfWork.GetDbContext<ApplicationContext>();
foreach (var entry in context.ChangeTracker.Entries())
{
var result = await SaveParsedExam(model);
if (result.Status)
if (entry.State == Microsoft.EntityFrameworkCore.EntityState.Added)
{
return ApiResponse.Success("保存试题成功");
}
else
{
return ApiResponse.Error($"保存试题数据失败{result.Message}");
if(entry.Entity is Question newQues)
{
newQues.CreatorId = newAssi.CreatorId;
}
}
}
catch (Exception ex)
await _unitOfWork.SaveChangesAsync();
return ApiResponse.Success();
}
private async void ParseStruct(AssignmentStructDto assignmentStruct, Guid ParentID)
{
var newStruct = _mapper.Map<AssignmentStruct>(assignmentStruct);
newStruct.ParentStructId = Guid.Empty == ParentID ? null : ParentID;
await _examRepository.AddAsync(newStruct);
foreach (var item in assignmentStruct.AssignmentQuestions)
{
return ApiResponse.Error($"保存试题数据失败: {ex.Message}");
var newQuestion = _mapper.Map<Question>(item);
//newQuestion.ParentQuestionId = item.ParentQuestion == null ? null : item.ParentQuestion.Id;
await _examRepository.AddAsync(newQuestion);
//await ParseAssignmentQuestion(assignmentStruct, item, newQuestion);
}
foreach (var item in assignmentStruct.ChildrenGroups)
{
ParseStruct(item, assignmentStruct.Id);
}
}
private async Task ParseAssignmentQuestion(AssignmentStructDto assignmentStruct, QuestionDto item, Question newQuestion)
{
AssignmentQuestion newAssignQues = new AssignmentQuestion();
newAssignQues.QuestionId = newQuestion.Id;
newAssignQues.AssignmentStructId = assignmentStruct.Id;
newAssignQues.CreatedAt = DateTime.UtcNow;
newAssignQues.Score = item.Score;
await _examRepository.AddAsync(newAssignQues);
}
private void SetEntityIdsAndRelations(AssignmentStruct group, Guid? assignmentId, Guid creatorId)
{
group.Id = Guid.NewGuid();
group.AssignmentId = assignmentId;
foreach (var aq in group.AssignmentQuestions)
{
aq.Id = Guid.NewGuid();
aq.AssignmentStructId = group.Id;
aq.Question.Id = Guid.NewGuid();
aq.Question.CreatorId = creatorId;
aq.CreatedAt = DateTime.UtcNow;
// ... 其他默认值
}
foreach (var childGroup in group.ChildrenGroups)
{
// 子题组的 AssignmentId 为 null通过 ParentGroup 关联
SetEntityIdsAndRelations(childGroup, null, creatorId);
childGroup.ParentStructId = group.Id;
}
}
public async Task<AssignmentDto> GetExamByIdAsync(Guid id)
{
var assignment = await _examRepository.GetFullExamByIdAsync(id);
if (assignment == null)
{
throw new InvalidOperationException("");
}
return _mapper.Map<AssignmentDto>(assignment);
}
public async Task<IEnumerable<AssignmentDto>> GetAllExamPreviewsAsync(Guid userId)
{
var assignments = await _examRepository.GetExamPreviewsByUserAsync(userId);
return _mapper.Map<IEnumerable<AssignmentDto>>(assignments);
}
public Task<ApiResponse> GetAllAsync(QueryParameter query)
{
throw new NotImplementedException();
}
public Task<ApiResponse> GetAsync(Guid id)
{
throw new NotImplementedException();
}
public Task<ApiResponse> AddAsync(AssignmentDto model)
{
throw new NotImplementedException();
}
public Task<ApiResponse> UpdateAsync(AssignmentDto model)
{
throw new NotImplementedException();
}
public Task<ApiResponse> DeleteAsync(Guid id)
@@ -58,392 +151,10 @@ namespace TechHelper.Server.Services
throw new NotImplementedException();
}
public Task<ApiResponse> GetAllAsync(QueryParameter query)
Task<ApiResponse> IExamService.GetAllExamPreviewsAsync(Guid userId)
{
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)
{
if (qgDto.ValidQuestionGroup)
{
await SaveQuestionGroup(qgDto);
}
else
{
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);
}
}
}
private async Task SaveQuestionGroup(QuestionGroupDto qgDto)
{
}
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}, 请稍后再试");
}
}
}
}