change
This commit is contained in:
@@ -2,22 +2,46 @@
|
||||
{
|
||||
public class ApiResponse
|
||||
{
|
||||
public ApiResponse(string message, bool status = false)
|
||||
public string Message { get; set; }
|
||||
public bool Status { get; set; }
|
||||
public object? Result { get; set; }
|
||||
|
||||
private ApiResponse(bool status, string message, object? result)
|
||||
{
|
||||
this.Message = message;
|
||||
this.Status = status;
|
||||
Status = status;
|
||||
Message = message;
|
||||
Result = result;
|
||||
}
|
||||
|
||||
public ApiResponse(string message, bool status = false)
|
||||
: this(status, message, null) { }
|
||||
|
||||
|
||||
public ApiResponse(bool status, object result)
|
||||
: this(status, string.Empty, result) { }
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个表示成功响应的 ApiResponse 实例。
|
||||
/// </summary>
|
||||
/// <param name="message">成功消息。</param>
|
||||
/// <param name="result">可选的返回数据。</param>
|
||||
/// <returns>ApiResponse 实例。</returns>
|
||||
public static ApiResponse Success(string message = "操作成功。", object? result = null)
|
||||
{
|
||||
this.Status = status;
|
||||
this.Result = result;
|
||||
return new ApiResponse(true, message, result);
|
||||
}
|
||||
|
||||
public string Message { get; set; }
|
||||
|
||||
public bool Status { get; set; }
|
||||
|
||||
public object Result { get; set; }
|
||||
/// <summary>
|
||||
/// 创建一个表示失败响应的 ApiResponse 实例。
|
||||
/// </summary>
|
||||
/// <param name="message">错误消息。</param>
|
||||
/// <param name="result">可选的错误详情或数据。</param>
|
||||
/// <returns>ApiResponse 实例。</returns>
|
||||
public static ApiResponse Error(string message = "操作失败。", object? result = null)
|
||||
{
|
||||
return new ApiResponse(false, message, result);
|
||||
}
|
||||
}
|
||||
}
|
306
TechHelper.Server/Services/ExamService.cs
Normal file
306
TechHelper.Server/Services/ExamService.cs
Normal file
@@ -0,0 +1,306 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,10 +2,10 @@
|
||||
{
|
||||
public interface IBaseService<T, TId>
|
||||
{
|
||||
Task<ApiResponse> GetAllAsync(QueryParameter query);
|
||||
Task<ApiResponse> GetAsync(TId id);
|
||||
Task<ApiResponse> AddAsync(T model);
|
||||
Task<ApiResponse> UpdateAsync(T model);
|
||||
Task<ApiResponse> DeleteAsync(TId id);
|
||||
Task<TechHelper.Services.ApiResponse> GetAllAsync(QueryParameter query);
|
||||
Task<TechHelper.Services.ApiResponse> GetAsync(TId id);
|
||||
Task<TechHelper.Services.ApiResponse> AddAsync(T model);
|
||||
Task<TechHelper.Services.ApiResponse> UpdateAsync(T model);
|
||||
Task<TechHelper.Services.ApiResponse> DeleteAsync(TId id);
|
||||
}
|
||||
}
|
||||
|
10
TechHelper.Server/Services/IExamService.cs
Normal file
10
TechHelper.Server/Services/IExamService.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Entities.DTO;
|
||||
using TechHelper.Services;
|
||||
|
||||
namespace TechHelper.Server.Services
|
||||
{
|
||||
public interface IExamService : IBaseService<ExamDto, Guid>
|
||||
{
|
||||
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user