重构项目结构,移除Assignment相关功能,优化Submission模块
Some checks failed
TechAct / explore-gitea-actions (push) Failing after 12s
Some checks failed
TechAct / explore-gitea-actions (push) Failing after 12s
This commit is contained in:
11
TechHelper.Server/Services/Question/IQuestionService.cs
Normal file
11
TechHelper.Server/Services/Question/IQuestionService.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Entities.Contracts;
|
||||
using Entities.DTO;
|
||||
|
||||
namespace TechHelper.Services.Beta
|
||||
{
|
||||
public interface IQuestionService : IBaseService<QuestionDto, Guid>
|
||||
{
|
||||
Task<ApiResponse> FindByTitle(string title);
|
||||
Task<ApiResponse> CheckTitlesExistence(IEnumerable<string> titles);
|
||||
}
|
||||
}
|
||||
302
TechHelper.Server/Services/Question/QuestionService.cs
Normal file
302
TechHelper.Server/Services/Question/QuestionService.cs
Normal file
@@ -0,0 +1,302 @@
|
||||
using AutoMapper;
|
||||
using Entities.Contracts;
|
||||
using SharedDATA.Api;
|
||||
using System.Linq.Expressions;
|
||||
using System.Linq;
|
||||
using Entities.DTO;
|
||||
|
||||
namespace TechHelper.Services.Beta
|
||||
{
|
||||
public class QuestionService : IQuestionService
|
||||
{
|
||||
private readonly IUnitOfWork _work;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IExamService _examService;
|
||||
|
||||
public QuestionService(IUnitOfWork work, IMapper mapper, IExamService examService)
|
||||
{
|
||||
_work = work;
|
||||
_mapper = mapper;
|
||||
_examService = examService;
|
||||
}
|
||||
|
||||
#region CRUD Operations
|
||||
|
||||
/// <summary>
|
||||
/// 创建新题目
|
||||
/// </summary>
|
||||
/// <param name="model">题目DTO</param>
|
||||
/// <returns>API响应结果</returns>
|
||||
public async Task<ApiResponse> AddAsync(QuestionDto model)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 业务逻辑校验:检查题目是否已存在
|
||||
var existingQuestion = await _work.GetRepository<Question>().GetFirstOrDefaultAsync(
|
||||
predicate: q => q.Title == model.Title && !q.IsDeleted
|
||||
);
|
||||
|
||||
if (existingQuestion != null)
|
||||
{
|
||||
return ApiResponse.Error($"题目 '{model.Title}' 已存在,请勿重复添加。");
|
||||
}
|
||||
|
||||
var newQuestion = _mapper.Map<Question>(model);
|
||||
|
||||
// 插入数据
|
||||
await _work.GetRepository<Question>().InsertAsync(newQuestion);
|
||||
await _work.SaveChangesAsync();
|
||||
|
||||
return ApiResponse.Success("题目添加成功。", newQuestion);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ApiResponse.Error($"添加题目失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID获取题目
|
||||
/// </summary>
|
||||
/// <param name="id">题目ID</param>
|
||||
/// <returns>API响应结果</returns>
|
||||
public async Task<ApiResponse> GetAsync(Guid id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var question = await _work.GetRepository<Question>().GetFirstOrDefaultAsync(
|
||||
predicate: q => q.Id == id && !q.IsDeleted
|
||||
);
|
||||
|
||||
if (question == null)
|
||||
{
|
||||
return ApiResponse.Error($"找不到 ID 为 {id} 的题目。");
|
||||
}
|
||||
|
||||
return ApiResponse.Success(result: question);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ApiResponse.Error($"获取题目时发生错误: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有题目(支持分页和搜索)
|
||||
/// </summary>
|
||||
/// <param name="query">查询参数</param>
|
||||
/// <returns>API响应结果</returns>
|
||||
public async Task<ApiResponse> GetAllAsync(QueryParameter query)
|
||||
{
|
||||
try
|
||||
{
|
||||
var repository = _work.GetRepository<Question>();
|
||||
|
||||
if (query.Search != null && !string.IsNullOrWhiteSpace(query.Search))
|
||||
{
|
||||
var questions = await repository.GetPagedListAsync(
|
||||
predicate: q => q.Title.Contains(query.Search) && !q.IsDeleted,
|
||||
pageSize: query.PageSize,
|
||||
pageIndex: query.PageIndex
|
||||
);
|
||||
var questionDtosFiltered = _mapper.Map<IEnumerable<QuestionDto>>(questions.Items);
|
||||
return new ApiResponse(true, questionDtosFiltered);
|
||||
}
|
||||
else
|
||||
{
|
||||
var questions = await repository.GetPagedListAsync(
|
||||
predicate: q => !q.IsDeleted,
|
||||
pageSize: query.PageSize,
|
||||
pageIndex: query.PageIndex
|
||||
);
|
||||
var questionDtos = _mapper.Map<IEnumerable<QuestionDto>>(questions.Items);
|
||||
return new ApiResponse(true, questionDtos);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new ApiResponse($"获取所有题目时发生错误: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新题目信息
|
||||
/// </summary>
|
||||
/// <param name="model">题目DTO</param>
|
||||
/// <returns>API响应结果</returns>
|
||||
public async Task<ApiResponse> UpdateAsync(QuestionDto model)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查题目是否存在
|
||||
var existingQuestion = await _work.GetRepository<Question>().GetFirstOrDefaultAsync(
|
||||
predicate: q => q.Id == model.Id && !q.IsDeleted
|
||||
);
|
||||
|
||||
if (existingQuestion == null)
|
||||
{
|
||||
return ApiResponse.Error($"找不到 ID 为 {model.Id} 的题目,无法更新。");
|
||||
}
|
||||
|
||||
// 检查题目文本是否与其他题目重复
|
||||
var duplicateCheck = await _work.GetRepository<Question>().GetFirstOrDefaultAsync(
|
||||
predicate: q => q.Id != model.Id && q.Title == model.Title && !q.IsDeleted
|
||||
);
|
||||
|
||||
if (duplicateCheck != null)
|
||||
{
|
||||
return ApiResponse.Error($"题目文本 '{model.Title}' 已被其他题目占用,请修改。");
|
||||
}
|
||||
|
||||
// 使用mapper更新实体属性,保留系统字段
|
||||
_mapper.Map(model, existingQuestion);
|
||||
existingQuestion.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
// 更新数据
|
||||
_work.GetRepository<Question>().Update(existingQuestion);
|
||||
await _work.SaveChangesAsync();
|
||||
|
||||
return ApiResponse.Success("题目更新成功。", existingQuestion);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ApiResponse.Error($"更新题目失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除题目(软删除)
|
||||
/// </summary>
|
||||
/// <param name="id">题目ID</param>
|
||||
/// <returns>API响应结果</returns>
|
||||
public async Task<ApiResponse> DeleteAsync(Guid id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var questionToDelete = await _work.GetRepository<Question>().GetFirstOrDefaultAsync(
|
||||
predicate: q => q.Id == id && !q.IsDeleted
|
||||
);
|
||||
|
||||
if (questionToDelete == null)
|
||||
{
|
||||
return ApiResponse.Error($"找不到 ID 为 {id} 的题目,或者该题目已被删除。");
|
||||
}
|
||||
|
||||
// 软删除
|
||||
questionToDelete.IsDeleted = true;
|
||||
questionToDelete.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
_work.GetRepository<Question>().Update(questionToDelete);
|
||||
await _work.SaveChangesAsync();
|
||||
|
||||
return ApiResponse.Success($"ID 为 {id} 的题目已成功删除 (软删除)。");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ApiResponse.Error($"删除题目时发生错误: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Business Logic Methods
|
||||
|
||||
/// <summary>
|
||||
/// 根据标题查找题目
|
||||
/// </summary>
|
||||
/// <param name="title">题目标题</param>
|
||||
/// <returns>API响应结果</returns>
|
||||
public async Task<ApiResponse> FindByTitle(string title)
|
||||
{
|
||||
try
|
||||
{
|
||||
var question = await _work.GetRepository<Question>().GetFirstOrDefaultAsync(
|
||||
predicate: q => q.Title == title && !q.IsDeleted
|
||||
);
|
||||
|
||||
if (question == null)
|
||||
{
|
||||
return ApiResponse.Error($"未找到题目 '{title}'。");
|
||||
}
|
||||
|
||||
return ApiResponse.Success(result: question);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ApiResponse.Error($"查找题目时出现问题: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量检查题目标题是否存在
|
||||
/// </summary>
|
||||
/// <param name="titles">题目标题集合</param>
|
||||
/// <returns>API响应结果</returns>
|
||||
public async Task<ApiResponse> CheckTitlesExistence(IEnumerable<string> titles)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (titles == null || !titles.Any())
|
||||
{
|
||||
return ApiResponse.Success("未指定查询的题目文本,返回空结果。", new Dictionary<string, bool>());
|
||||
}
|
||||
|
||||
var distinctTitles = titles.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
|
||||
|
||||
var existingQuestions = await _work.GetRepository<Question>().GetAllAsync(
|
||||
predicate: q => distinctTitles.Contains(q.Title) && !q.IsDeleted
|
||||
);
|
||||
|
||||
var existingQuestionTexts = new HashSet<string>(
|
||||
existingQuestions.Select(q => q.Title),
|
||||
StringComparer.OrdinalIgnoreCase
|
||||
);
|
||||
|
||||
var resultDictionary = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var title in titles)
|
||||
{
|
||||
resultDictionary[title] = existingQuestionTexts.Contains(title);
|
||||
}
|
||||
|
||||
return ApiResponse.Success(result: resultDictionary);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ApiResponse.Error($"批量查找题目存在性时出现问题: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表达式构建器扩展类
|
||||
/// </summary>
|
||||
public static class PredicateBuilder
|
||||
{
|
||||
public static Expression<Func<T, bool>> And<T>(
|
||||
this Expression<Func<T, bool>> first,
|
||||
Expression<Func<T, bool>> second)
|
||||
{
|
||||
var invokedExpr = Expression.Invoke(second, first.Parameters.Cast<Expression>());
|
||||
return Expression.Lambda<Func<T, bool>>(
|
||||
Expression.AndAlso(first.Body, invokedExpr),
|
||||
first.Parameters
|
||||
);
|
||||
}
|
||||
|
||||
public static Expression<Func<T, bool>> Or<T>(
|
||||
this Expression<Func<T, bool>> first,
|
||||
Expression<Func<T, bool>> second)
|
||||
{
|
||||
var invokedExpr = Expression.Invoke(second, first.Parameters.Cast<Expression>());
|
||||
return Expression.Lambda<Func<T, bool>>(
|
||||
Expression.OrElse(first.Body, invokedExpr),
|
||||
first.Parameters
|
||||
);
|
||||
}
|
||||
|
||||
public static Expression<Func<T, bool>> True<T>() => f => true;
|
||||
public static Expression<Func<T, bool>> False<T>() => f => false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user