using YD_Event.Application.Project.Dtos; using YD_Event.Core.Entities; using YD_Event.Core.Enum; using Mapster; using SqlSugar; using Microsoft.Extensions.Logging; using Easy.Core; namespace YD_Event.Application.Project; /// /// 项目管理 /// public class ProjectService : BaseService, ITransient { private readonly ISqlSugarRepository _repository; private readonly ISqlSugarRepository _groupRepository; private readonly ISqlSugarRepository _eventProjectRepository; private readonly ISqlSugarRepository _participationRecordRepository; private readonly ISqlSugarRepository _scoreRuleRepository; private readonly IIdGenerator _idGenerator; private readonly ILogger _logger; public ProjectService( ISqlSugarRepository repository, ISqlSugarRepository groupRepository, ISqlSugarRepository eventProjectRepository, ISqlSugarRepository participationRecordRepository, ISqlSugarRepository scoreRuleRepository, IIdGenerator idGenerator, ILogger logger) : base(repository) { _repository = repository; _groupRepository = groupRepository; _eventProjectRepository = eventProjectRepository; _participationRecordRepository = participationRecordRepository; _scoreRuleRepository = scoreRuleRepository; _idGenerator = idGenerator; _logger = logger; } /// /// 项目分页查询 /// [DisplayName("项目分页查询")] public async Task> Page(ProjectQueryInput input) { var query = _repository.AsQueryable() .WhereIF(!string.IsNullOrWhiteSpace(input.ProjectName), p => p.Name.Contains(input.ProjectName)) .WhereIF(input.ProjectType.HasValue, p => p.ProjectType == (ProjectTypeEnum)input.ProjectType.Value) .OrderByDescending(p => p.Id); var list = await query.Select(p => new ProjectDto { Id = p.Id, Name = p.Name, ProjectType = (long)p.ProjectType, CompletionCountType = p.CompletionCountType, CompletionCount = p.CompletionCount, CompletionCountMin = p.CompletionCountMin, CompletionCountMax = p.CompletionCountMax, Fee = p.Fee, DurationMinutes = p.DurationMinutes, DurationSeconds = p.DurationSeconds, ScoreRuleId = p.ScoreRuleId, Remark = p.Remark, VenueCount = p.VenueCount, SortIndex = p.SortIndex, Status = p.Status, DeleteMark = p.DeleteMark, CreatedUserId = p.CreatedUserId, CreatedTime = p.CreatedTime, UpdatedUserId = p.UpdatedUserId, UpdatedTime = p.UpdatedTime }) .ToPagedListAsync(input); foreach (var item in list.Rows) { item.ProjectTypeName = ((ProjectTypeEnum)item.ProjectType).Description(); // 生成完成人数显示文本 if (item.CompletionCountType == 0) { item.CompletionCountDisplay = item.CompletionCount.HasValue ? $"{item.CompletionCount.Value}人" : "0人"; } else { var min = item.CompletionCountMin ?? 0; var max = item.CompletionCountMax ?? 0; item.CompletionCountDisplay = $"{min}-{max}人"; } } var ruleIds = list.Rows.Where(x => x.ScoreRuleId.HasValue).Select(x => x.ScoreRuleId!.Value).Distinct().ToList(); var ruleMap = (await _scoreRuleRepository.AsQueryable().Where(x => ruleIds.Contains(x.Id)).ToListAsync()).ToDictionary(x => x.Id, x => x.RuleName); foreach (var item in list.Rows) if (item.ScoreRuleId.HasValue) item.ScoreRuleName = ruleMap.GetValueOrDefault(item.ScoreRuleId.Value); return list; } /// /// 添加项目 /// [DisplayName("添加项目")] [UnitOfWork] public async Task Add(AddProjectInput input) { await ValidateScoreRule(input.ScoreRuleId); // 验证完成人数字段 if (input.CompletionCountType == 0) { // 单一值模式 if (!input.CompletionCount.HasValue || input.CompletionCount.Value < 0) { throw Oops.Bah("请填写有效的完成人数"); } } else { // 范围值模式 if (!input.CompletionCountMin.HasValue || !input.CompletionCountMax.HasValue) { throw Oops.Bah("请填写完成人数最小值和最大值"); } if (input.CompletionCountMin.Value < 0 || input.CompletionCountMax.Value < 0) { throw Oops.Bah("完成人数不能为负数"); } if (input.CompletionCountMin.Value > input.CompletionCountMax.Value) { throw Oops.Bah("完成人数最小值不能大于最大值"); } } var entity = input.Adapt(); entity.Id = _idGenerator.NewLong(); entity.CreatedTime = DateTime.Now; entity.Status = Core.Enum.AvailabilityStatus.Enable; await _repository.InsertAsync(entity); if (input.Groups != null && input.Groups.Any()) { var groups = input.Groups.Select(g => { var group = g.Adapt(); group.Id = _idGenerator.NewLong(); group.ProjectId = entity.Id; return group; }).ToList(); await _groupRepository.InsertRangeAsync(groups); } } /// /// 更新项目 /// [DisplayName("更新项目")] [UnitOfWork] public async Task Update(UpdateProjectInput input) { await ValidateScoreRule(input.ScoreRuleId); var entity = await _repository.GetByIdAsync(input.Id); if (entity == null) throw new InvalidOperationException("项目不存在"); // 验证完成人数字段 if (input.CompletionCountType == 0) { // 单一值模式 if (!input.CompletionCount.HasValue || input.CompletionCount.Value < 0) { throw Oops.Bah("请填写有效的完成人数"); } } else { // 范围值模式 if (!input.CompletionCountMin.HasValue || !input.CompletionCountMax.HasValue) { throw Oops.Bah("请填写完成人数最小值和最大值"); } if (input.CompletionCountMin.Value < 0 || input.CompletionCountMax.Value < 0) { throw Oops.Bah("完成人数不能为负数"); } if (input.CompletionCountMin.Value > input.CompletionCountMax.Value) { throw Oops.Bah("完成人数最小值不能大于最大值"); } } input.Adapt(entity); entity.UpdatedTime = DateTime.Now; await _repository.UpdateAsync(entity); await _groupRepository.SoftDeleteAsync(x => x.ProjectId == entity.Id); if (input.Groups != null && input.Groups.Any()) { var groups = input.Groups.Select(g => { var group = g.Adapt(); group.Id = _idGenerator.NewLong(); group.ProjectId = entity.Id; return group; }).ToList(); await _groupRepository.InsertRangeAsync(groups); } } /// /// 删除项目 /// [DisplayName("删除项目")] [UnitOfWork] public async Task Delete(List ids) { _logger.LogInformation("开始删除项目:项目ID列表={Ids}", string.Join(",", ids)); // 检查项目是否被赛事使用 var hasEventProjects = await _eventProjectRepository.IsAnyAsync(x => ids.Contains(x.ProjectId)); if (hasEventProjects) { _logger.LogWarning("删除失败:部分项目正在被赛事使用"); throw new InvalidOperationException("部分项目正在被赛事使用,无法删除。请先从赛事中移除这些项目。"); } // 检查是否有报名数据关联 var eventProjectIds = await _eventProjectRepository.AsQueryable() .Where(x => ids.Contains(x.ProjectId)) .Select(x => x.Id) .ToListAsync(); if (eventProjectIds.Any()) { var hasParticipations = await _participationRecordRepository.IsAnyAsync(x => eventProjectIds.Contains(x.ProjectId) && !x.DeleteMark); if (hasParticipations) { _logger.LogWarning("删除失败:部分项目已有报名数据"); throw new InvalidOperationException("部分项目已有报名数据,为保证数据完整性,无法删除。"); } } // 执行删除 - 软删 await _repository.SoftDeleteByIdsAsync(ids); await _groupRepository.SoftDeleteAsync(x => ids.Contains(x.ProjectId)); _logger.LogInformation("删除项目成功:项目ID列表={Ids}", string.Join(",", ids)); } /// /// 获取项目详情 /// [DisplayName("获取项目详情")] public async Task Detail(long id) { var p = await _repository.GetByIdAsync(id); if (p == null) throw new InvalidOperationException("项目不存在"); var dto = p.Adapt(); dto.ProjectType = (long)p.ProjectType; dto.ProjectTypeName = p.ProjectType.Description(); if (p.ScoreRuleId.HasValue) dto.ScoreRuleName = (await _scoreRuleRepository.GetByIdAsync(p.ScoreRuleId.Value))?.RuleName; // 生成完成人数显示文本 if (dto.CompletionCountType == 0) { dto.CompletionCountDisplay = dto.CompletionCount.HasValue ? $"{dto.CompletionCount.Value}人" : "0人"; } else { var min = dto.CompletionCountMin ?? 0; var max = dto.CompletionCountMax ?? 0; dto.CompletionCountDisplay = $"{min}-{max}人"; } var groups = await _groupRepository.GetListAsync(x => x.ProjectId == id); dto.Groups = groups.Adapt>(); return dto; } private async Task ValidateScoreRule(long? scoreRuleId) { if (!scoreRuleId.HasValue || !await _scoreRuleRepository.IsAnyAsync(x => x.Id == scoreRuleId.Value && x.Status == AvailabilityStatus.Enable)) throw Oops.Bah("请选择有效的计分规则"); } /// /// 项目统计类型 /// public enum ProjectStatisticType { /// /// 参赛人数 /// ParticipationCount = 1, /// /// 完成人数 /// CompletionCount = 2 } /// /// 获取项目统计数据(单个项目) /// /// 项目ID /// 统计类型 /// 统计数值 [DisplayName("获取项目统计数据")] public async Task GetProjectStatisticAsync(long projectId, ProjectStatisticType statisticType) { if (projectId <= 0) { _logger.LogWarning("Invalid projectId: {ProjectId}", projectId); return 0; } var eventProjectIds = await _eventProjectRepository.AsQueryable() .Where(ep => ep.ProjectId == projectId) .Select(ep => ep.Id) .ToListAsync(); if (!eventProjectIds.Any()) { return 0; } var query = _participationRecordRepository.AsQueryable() .Where(pr => eventProjectIds.Contains(pr.ProjectId) && !pr.DeleteMark); switch (statisticType) { case ProjectStatisticType.ParticipationCount: query = query.Where(pr => pr.ParticipationStatus == ParticipationStatus.Participated || pr.ParticipationStatus == ParticipationStatus.Completed); break; case ProjectStatisticType.CompletionCount: query = query.Where(pr => pr.ParticipationStatus == ParticipationStatus.Completed); break; default: _logger.LogWarning("Unknown statistic type: {StatisticType}", statisticType); return 0; } var count = await query.CountAsync(); return count; } /// /// 批量获取项目统计数据(性能优化,避免N+1查询) /// /// 项目ID列表 /// 统计类型 /// 字典,Key为项目ID,Value为统计数值 [DisplayName("批量获取项目统计数据")] public async Task> GetProjectStatisticsBatchAsync(List projectIds, ProjectStatisticType statisticType) { var result = new Dictionary(); if (projectIds == null || !projectIds.Any()) { return result; } foreach (var projectId in projectIds) { result[projectId] = 0; } var eventProjects = await _eventProjectRepository.AsQueryable() .Where(ep => projectIds.Contains(ep.ProjectId)) .Select(ep => new { ep.Id, ep.ProjectId }) .ToListAsync(); if (!eventProjects.Any()) { return result; } var eventProjectIds = eventProjects.Select(ep => ep.Id).ToList(); var query = _participationRecordRepository.AsQueryable() .Where(pr => eventProjectIds.Contains(pr.ProjectId) && !pr.DeleteMark); switch (statisticType) { case ProjectStatisticType.ParticipationCount: query = query.Where(pr => pr.ParticipationStatus == ParticipationStatus.Participated || pr.ParticipationStatus == ParticipationStatus.Completed); break; case ProjectStatisticType.CompletionCount: query = query.Where(pr => pr.ParticipationStatus == ParticipationStatus.Completed); break; default: _logger.LogWarning("Unknown statistic type: {StatisticType}", statisticType); return result; } var records = await query.ToListAsync(); foreach (var record in records) { var projectId = eventProjects.FirstOrDefault(ep => ep.Id == record.ProjectId)?.ProjectId; if (projectId.HasValue && result.ContainsKey(projectId.Value)) { result[projectId.Value]++; } } return result; } }