From 8876a7e0867a23be126aa12ff2826ce196529711 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E6=A2=93=E5=BD=AD?= Date: Mon, 13 Jul 2026 19:34:42 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=8A=A5=E5=90=8D=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Event/UnifiedRegistrationService.cs | 440 +++++++++++------- .../Event/UserEventRegistrationService.cs | 46 +- 2 files changed, 297 insertions(+), 189 deletions(-) diff --git a/YD_Event.Application/Event/UnifiedRegistrationService.cs b/YD_Event.Application/Event/UnifiedRegistrationService.cs index d6d8949..a53e8fc 100644 --- a/YD_Event.Application/Event/UnifiedRegistrationService.cs +++ b/YD_Event.Application/Event/UnifiedRegistrationService.cs @@ -12,6 +12,7 @@ using YD_Event.Core.Extensions; using YD_Event.Core.Infrastructure.WeChat; using YD_Event.Core.Infrastructure.WeChat.Dto; using YD_Event.Core.Options; +using EasyCaching.Core; namespace YD_Event.Application.Event; @@ -51,6 +52,15 @@ public class UnifiedRegistrationService : ITransient private readonly RefundConfigService _refundConfigService; private readonly ISqlSugarRepository _refundRecordRepository; private readonly EventRuleSnapshotService _ruleSnapshotService; + private readonly IEasyCachingProvider _easyCachingProvider; + + // 缓存键前缀 + private const string CacheKey_EventConfig = "EventConfig:"; + private const string CacheKey_EventLimit = "EventLimit:"; + private const string CacheKey_ProjectLimit = "EventProjectLimit:"; + private const string CacheKey_EventProjects = "EventProjects:"; + private const string CacheKey_ProjectGroups = "EventProjectGroups:"; + private const string CacheKey_BaseProjects = "BaseProjects:"; public UnifiedRegistrationService( ISqlSugarRepository eventRepository, @@ -83,7 +93,8 @@ public class UnifiedRegistrationService : ITransient OrderNoGeneratorService orderNoGeneratorService, RefundConfigService refundConfigService, ISqlSugarRepository refundRecordRepository, - EventRuleSnapshotService ruleSnapshotService) + EventRuleSnapshotService ruleSnapshotService, + IEasyCachingProvider easyCachingProvider) { _eventRepository = eventRepository; _unitRepository = unitRepository; @@ -116,6 +127,7 @@ public class UnifiedRegistrationService : ITransient _refundConfigService = refundConfigService; _refundRecordRepository = refundRecordRepository; _ruleSnapshotService = ruleSnapshotService; + _easyCachingProvider = easyCachingProvider; } /// @@ -1387,10 +1399,84 @@ public class UnifiedRegistrationService : ITransient _logger.LogInformation("已删除预报名记录,报名ID:{RegistrationId}", registration.Id); } + /// + /// 获取赛事报名限制配置(带缓存,3分钟过期) + /// + private async Task GetEventLimitCacheAsync(long eventId) + { + var cacheKey = $"{CacheKey_EventLimit}{eventId}"; + var cacheValue = await _easyCachingProvider.GetAsync(cacheKey); + if (cacheValue.HasValue) return cacheValue.Value; + + var limit = await _limitRepository.GetFirstAsync(x => x.EventId == eventId && !x.DeleteMark); + await _easyCachingProvider.SetAsync(cacheKey, limit, TimeSpan.FromMinutes(3)); + return limit; + } + + /// + /// 获取赛事项目限制配置列表(带缓存,3分钟过期) + /// + private async Task> GetProjectLimitsCacheAsync(long eventId) + { + var cacheKey = $"{CacheKey_ProjectLimit}{eventId}"; + var cacheValue = await _easyCachingProvider.GetAsync>(cacheKey); + if (cacheValue.HasValue) return cacheValue.Value; + + var projectLimits = await _projectLimitRepository.GetListAsync(x => x.EventId == eventId && !x.DeleteMark); + await _easyCachingProvider.SetAsync(cacheKey, projectLimits, TimeSpan.FromMinutes(3)); + return projectLimits; + } + + /// + /// 获取赛事项目列表(带缓存,3分钟过期) + /// + private async Task> GetEventProjectsCacheAsync(long eventId) + { + var cacheKey = $"{CacheKey_EventProjects}{eventId}"; + var cacheValue = await _easyCachingProvider.GetAsync>(cacheKey); + if (cacheValue.HasValue) return cacheValue.Value; + + var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == eventId); + await _easyCachingProvider.SetAsync(cacheKey, eventProjects, TimeSpan.FromMinutes(3)); + return eventProjects; + } + + /// + /// 获取赛事项目组别列表(带缓存,3分钟过期) + /// + private async Task> GetEventProjectGroupsCacheAsync(long eventId) + { + var cacheKey = $"{CacheKey_ProjectGroups}{eventId}"; + var cacheValue = await _easyCachingProvider.GetAsync>(cacheKey); + if (cacheValue.HasValue) return cacheValue.Value; + + var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x => x.EventId == eventId && !x.DeleteMark); + await _easyCachingProvider.SetAsync(cacheKey, eventProjectGroups, TimeSpan.FromMinutes(3)); + return eventProjectGroups; + } + + /// + /// 获取基础项目列表(带缓存,3分钟过期) + /// + private async Task> GetBaseProjectsCacheAsync(List projectIds, long eventId) + { + var cacheKey = $"{CacheKey_BaseProjects}{eventId}"; + var cacheValue = await _easyCachingProvider.GetAsync>(cacheKey); + if (cacheValue.HasValue) return cacheValue.Value; + + var projects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id) && !x.DeleteMark); + await _easyCachingProvider.SetAsync(cacheKey, projects, TimeSpan.FromMinutes(3)); + return projects; + } + /// /// 检查报名限制 /// - public async Task CheckRegistrationLimitAsync(UnifiedRegistrationInput input, long? excludeRegistrationId = null) + public async Task CheckRegistrationLimitAsync( + UnifiedRegistrationInput input, + long? excludeRegistrationId = null, + List? eventProjects = null, + List? eventProjectGroups = null) { var result = new RegistrationLimitCheckOutput { @@ -1400,50 +1486,14 @@ public class UnifiedRegistrationService : ITransient _logger.LogInformation("开始检查报名限制,赛事ID: {EventId}", input.EventId); - var limit = await _limitRepository.GetFirstAsync(x => x.EventId == input.EventId && !x.DeleteMark); - var projectLimits = await _projectLimitRepository.GetListAsync(x => x.EventId == input.EventId && !x.DeleteMark); - var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == input.EventId); - var projects = await _projectRepository.GetListAsync(x => eventProjects.Select(ep => ep.ProjectId).Contains(x.Id) && !x.DeleteMark); - var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x => x.EventId == input.EventId && !x.DeleteMark); - - // 先获取所有报名信息,供后续检查使用 - // 线上支付类型的报名记录,仅统计PaymentStatus == 1(已支付)的记录 - // 线下支付类型的报名记录: - // - 银行转账方式:仅统计PaymentStatus == 1(已支付)和PaymentStatus == 0(待审核)的记录, - // 不统计PaymentStatus == 4(待确认转账)和PaymentStatus == 5(待支付确认)的记录 - // - 其他线下方式:统计PaymentStatus == 1(已支付)和PaymentStatus == 0(待审核)的记录 - var eventRegistrationsQuery = _registrationRepository.AsQueryable().Where(x => - x.EventId == input.EventId && !x.DeleteMark && - ( - // 线上支付:仅已支付 - (x.PaymentMethod == 2 && x.PaymentStatus == 1) || - // 线下支付: - (x.PaymentMethod == 1 && ( - // 已支付的都统计 - x.PaymentStatus == 1 || - // 待审核的都统计 - x.PaymentStatus == 0 - )) - ) && - // 排除待支付确认的记录(状态5)、待确认转账的记录(状态4)、支付成功但名额已满的记录(状态6) - x.PaymentStatus != 4 && x.PaymentStatus != 5 && x.PaymentStatus != 6 - ); - - // 如果指定了要排除的报名ID,则排除该记录 - if (excludeRegistrationId.HasValue) - { - eventRegistrationsQuery = eventRegistrationsQuery.Where(x => x.Id != excludeRegistrationId.Value); - } - - var eventRegistrations = await eventRegistrationsQuery.ToListAsync(); - var eventRegistrationIds = eventRegistrations.Select(r => r.Id).ToList(); - var registrationPersons = await _registrationPersonRepository.GetListAsync(x => - eventRegistrationIds.Contains(x.RegistrationId) && !x.DeleteMark); - var allRegistrationItems = await _registrationItemRepository.GetListAsync(x => - eventRegistrationIds.Contains(x.RegistrationId) && !x.DeleteMark); + var limit = await GetEventLimitCacheAsync(input.EventId); + var projectLimits = await GetProjectLimitsCacheAsync(input.EventId); + eventProjects ??= await GetEventProjectsCacheAsync(input.EventId); + var projects = await GetBaseProjectsCacheAsync(eventProjects.Select(ep => ep.ProjectId).Distinct().ToList(), input.EventId); + eventProjectGroups ??= await GetEventProjectGroupsCacheAsync(input.EventId); - _logger.LogInformation("获取到赛事报名记录: {RegistrationCount},报名人员: {PersonCount},报名项目: {ItemCount}", - eventRegistrations.Count, registrationPersons.Count, allRegistrationItems.Count); + _logger.LogInformation("获取赛事配置完成,项目数: {ProjectCount}, 组別数: {GroupCount}", + eventProjects.Count, eventProjectGroups.Count); // ==================== 报名限制检查流程 ==================== // 检查顺序说明: @@ -1455,27 +1505,26 @@ public class UnifiedRegistrationService : ITransient // 6. 最后检查项目完成人数 - 确保人数与项目的完成人数完全一致 // 检查1:团队组别容量限制 - // 验证是否有组别超过了限报的团队数量(例如:某组别限报2队,但本次报了3队) CheckTeamGroupCapacityLimitAsync(input, result, projectLimits, eventProjects, projects, eventProjectGroups); + if (result.HasLimit) return result; - // 检查2:成员项目限报 - // 验证队员是否超过了个人项目或团队项目的限报数量(例如:某队员已报2个个人项目,限报2个,又报第3个) - await CheckMemberProjectLimitAsync(input, result, limit, eventProjects, projects, registrationPersons, allRegistrationItems); + // 检查2:成员项目限报(优化:使用数据库聚合查询,避免全量加载) + await CheckMemberProjectLimitAsync(input, result, limit, eventProjects, projects, excludeRegistrationId); + if (result.HasLimit) return result; - // 检查3:人员项目组别重复 - // 验证同一人员是否在本次报名中重复选择了同一项目的多个组别(例如:同一队员在同一项目的男子组和女子组都报了名) + // 检查3:人员项目组别重复(纯内存操作,输入数据验证) CheckPersonProjectDuplicateAsync(input, result, eventProjects, projects, eventProjectGroups); + if (result.HasLimit) return result; - // 检查4:人员是否已在其他报名中报名 - // 验证人员是否已在其他已支付的报名记录中报过同一项目组别(避免重复占用名额) - CheckPersonAlreadyRegisteredAsync(input, result, eventProjects, projects, eventProjectGroups, registrationPersons, allRegistrationItems, eventRegistrationIds); + // 检查4:人员是否已在其他报名中报名(优化:使用数据库查询,避免全量加载) + await CheckPersonAlreadyRegisteredAsync(input, result, eventProjects, projects, eventProjectGroups, excludeRegistrationId); + if (result.HasLimit) return result; - // 检查5:团队成员冲突检测 - // 验证同一人员是否在同一项目的多个团队中(例如:同一队员既在1队又在2队) + // 检查5:团队成员冲突检测(纯内存操作,输入数据验证) CheckTeamMemberConflictAsync(input, result, eventProjects, projects, eventProjectGroups); + if (result.HasLimit) return result; - // 检查6:项目完成人数验证 - // 验证用户选择的参与人数必须与项目的"完成人数"完全一致(例如:项目要求完成3人,但只选了2人或选了4人) + // 检查6:项目完成人数验证(纯内存操作,输入数据验证) CheckProjectCompletionAsync(input, result, eventProjects, projects, eventProjectGroups); _logger.LogInformation("报名限制检查完成,是否存在限制: {HasLimit}, 限制详情数: {LimitCount}", @@ -1590,7 +1639,7 @@ public class UnifiedRegistrationService : ITransient } /// - /// 检查成员项目限报 + /// 检查成员项目限报(优化版:使用数据库查询,仅查询涉及的成员数据,避免全量加载) /// private async Task CheckMemberProjectLimitAsync( UnifiedRegistrationInput input, @@ -1598,8 +1647,7 @@ public class UnifiedRegistrationService : ITransient EventRegistrationLimit? limit, List eventProjects, List projects, - List registrationPersons, - List allRegistrationItems) + long? excludeRegistrationId = null) { var memberIdsList = new List(); foreach (var item in input.Items) @@ -1617,101 +1665,103 @@ public class UnifiedRegistrationService : ITransient var members = await _userRepository.GetListAsync(x => memberIds.Contains(x.Id) && !x.DeleteMark); _logger.LogInformation("检查报名限制的成员数量: {MemberCount}", members.Count); + // 构建有效报名记录的子查询(与原逻辑一致的过滤条件) + var validRegistrationQuery = _registrationRepository.AsQueryable() + .Where(x => x.EventId == input.EventId && !x.DeleteMark && + ( + (x.PaymentMethod == 2 && x.PaymentStatus == 1) || + (x.PaymentMethod == 1 && (x.PaymentStatus == 1 || x.PaymentStatus == 0)) + ) && + x.PaymentStatus != 4 && x.PaymentStatus != 5 && x.PaymentStatus != 6); + + if (excludeRegistrationId.HasValue) + { + validRegistrationQuery = validRegistrationQuery.Where(x => x.Id != excludeRegistrationId.Value); + } + + // 查询这些成员在该赛事中已报名的项目(按用户+项目去重) + var existingMemberProjects = await _db.Queryable() + .InnerJoin((p, i) => p.RegistrationItemId == i.Id && !i.DeleteMark) + .InnerJoin(validRegistrationQuery, (p, i, r) => p.RegistrationId == r.Id) + .Where((p, i, r) => memberIds.Contains(p.UserId) && !p.DeleteMark) + .Select((p, i, r) => new + { + UserId = p.UserId, + ProjectId = i.ProjectId + }) + .Distinct() + .ToListAsync(); + + // 按用户分组统计已报名项目 + var memberProjectMap = existingMemberProjects + .GroupBy(x => x.UserId) + .ToDictionary( + g => g.Key, + g => g.Select(x => x.ProjectId).ToHashSet()); + + // 构建项目类型映射(赛事项目ID -> 基础项目类型) + var projectTypeMap = new Dictionary(); // key: eventProjectId, value: isIndividual + foreach (var ep in eventProjects) + { + var baseProject = projects.FirstOrDefault(p => p.Id == ep.ProjectId); + if (baseProject != null) + { + projectTypeMap[ep.Id] = baseProject.IsIndividualProject(); + } + } + + // 统计每个成员的个人/团队项目数 foreach (var member in members) { - _logger.LogDebug("检查成员报名限制,成员ID: {MemberId}, 姓名: {MemberName}", member.Id, member.Name); - - var (individualProjectCount, teamProjectCount) = CalculateMemberProjectCounts( - member.Id, eventProjects, projects, registrationPersons, allRegistrationItems, input); + var countedProjectIds = new HashSet(); + int individualProjectCount = 0; + int teamProjectCount = 0; + + // 统计已存在的报名项目 + if (memberProjectMap.TryGetValue(member.Id, out var existingProjectIds)) + { + foreach (var projectId in existingProjectIds) + { + if (!countedProjectIds.Add(projectId)) continue; + if (projectTypeMap.TryGetValue(projectId, out var isIndividual)) + { + if (isIndividual) + individualProjectCount++; + else + teamProjectCount++; + } + } + } + + // 统计本次提交的项目,确保不重复统计 + foreach (var item in input.Items) + { + var allPersonsInItem = GetAllPersonsFromItem(item); + if (!allPersonsInItem.Any(p => p.UserId.HasValue && p.UserId.Value == member.Id)) + continue; + + if (!countedProjectIds.Add(item.ProjectId)) continue; + + if (projectTypeMap.TryGetValue(item.ProjectId, out var isIndividual)) + { + if (isIndividual) + individualProjectCount++; + else + teamProjectCount++; + } + } _logger.LogDebug("成员 {MemberName} 个人项目报名数: {IndividualCount}, 团队项目报名数: {TeamCount}", member.Name, individualProjectCount, teamProjectCount); CheckIndividualProjectLimit(result, limit, member, individualProjectCount); + if (result.HasLimit) return; + CheckTeamProjectLimit(result, limit, member, teamProjectCount); + if (result.HasLimit) return; } } - /// - /// 计算成员项目报名数量(包含本次提交的项目) - /// - private (int individualCount, int teamCount) CalculateMemberProjectCounts( - long memberId, - List eventProjects, - List projects, - List registrationPersons, - List allRegistrationItems, - UnifiedRegistrationInput input) - { - var matchedPersons = registrationPersons.Where(x => x.UserId == memberId).ToList(); - var personRegistrationIds = matchedPersons.Select(x => x.RegistrationId).Distinct().ToList(); - var personRegistrationItems = allRegistrationItems.Where(ri => personRegistrationIds.Contains(ri.RegistrationId)).ToList(); - - int individualProjectCount = 0; - int teamProjectCount = 0; - - // 已统计过的项目ID(同一项目下的多个组别不会重复统计) - var countedProjectIds = new HashSet(); - - // 统计已存在的报名项目(去重:同一项目下的多个组别仅统计一次) - foreach (var item in personRegistrationItems) - { - if (!countedProjectIds.Add(item.ProjectId)) - { - continue; - } - - var eventProject = eventProjects.FirstOrDefault(ep => ep.Id == item.ProjectId); - if (eventProject == null) continue; - - var baseProject = projects.FirstOrDefault(p => p.Id == eventProject.ProjectId); - if (baseProject == null) continue; - - if (baseProject.IsIndividualProject()) - { - individualProjectCount++; - } - else if (baseProject.IsTeamProject()) - { - teamProjectCount++; - } - } - - // 统计本次提交的项目,确保不重复统计 - foreach (var item in input.Items) - { - // 检查该成员是否在本次提交的项目中 - var allPersonsInItem = GetAllPersonsFromItem(item); - if (!allPersonsInItem.Any(p => p.UserId.HasValue && p.UserId.Value == memberId)) - { - continue; - } - - // 检查是否已经统计过该项目(包含已有报名或本次提交中已统计过的项目) - if (!countedProjectIds.Add(item.ProjectId)) - { - continue; - } - - var eventProject = eventProjects.FirstOrDefault(ep => ep.Id == item.ProjectId); - if (eventProject == null) continue; - - var baseProject = projects.FirstOrDefault(p => p.Id == eventProject.ProjectId); - if (baseProject == null) continue; - - if (baseProject.IsIndividualProject()) - { - individualProjectCount++; - } - else if (baseProject.IsTeamProject()) - { - teamProjectCount++; - } - } - - return (individualProjectCount, teamProjectCount); - } - /// /// 检查个人项目限报 /// @@ -1838,60 +1888,96 @@ public class UnifiedRegistrationService : ITransient } /// - /// 检查人员是否已在其他报名中报过同一项目组别 + /// 检查人员是否已在其他报名中报过同一项目组别(优化版:使用数据库查询,避免全量加载) /// - private void CheckPersonAlreadyRegisteredAsync( + private async Task CheckPersonAlreadyRegisteredAsync( UnifiedRegistrationInput input, RegistrationLimitCheckOutput result, List eventProjects, List projects, List eventProjectGroups, - List registrationPersons, - List allRegistrationItems, - List eventRegistrationIds) + long? excludeRegistrationId = null) { - if (!eventRegistrationIds.Any()) - { - return; - } - + // 收集所有待检查的 (groupId, userId) 组合 + var checkPairs = new List<(long GroupId, long UserId, string UserName)>(); foreach (var item in input.Items) { if (!item.GroupId.HasValue) continue; - var eventProjectGroup = eventProjectGroups.FirstOrDefault(epg => epg.Id == item.GroupId.Value); - if (eventProjectGroup == null) continue; - - var eventProject = eventProjects.FirstOrDefault(ep => ep.Id == eventProjectGroup.EventProjectId); - if (eventProject == null) continue; - - var project = projects.FirstOrDefault(p => p.Id == eventProject.ProjectId); - var projectName = project?.Name ?? "未知项目"; - - var existingItems = allRegistrationItems.Where(ri => ri.EventProjectGroupId == item.GroupId.Value).ToList(); - if (!existingItems.Any()) continue; - - var existingItemIds = existingItems.Select(ri => ri.Id).ToList(); - var existingPersons = registrationPersons.Where(rp => existingItemIds.Contains(rp.RegistrationItemId)).ToList(); - var allPersonsInItem = GetAllPersonsFromItem(item); - foreach (var person in allPersonsInItem) { if (!person.UserId.HasValue) continue; - - var existingPerson = existingPersons.FirstOrDefault(ep => ep.UserId == person.UserId.Value); - if (existingPerson == null) continue; + checkPairs.Add((item.GroupId.Value, person.UserId.Value, person.Name ?? "未知人员")); + } + } + + if (!checkPairs.Any()) return; + + var groupIds = checkPairs.Select(p => p.GroupId).Distinct().ToList(); + var userIds = checkPairs.Select(p => p.UserId).Distinct().ToList(); + + // 构建有效报名记录的子查询 + var validRegistrationQuery = _registrationRepository.AsQueryable() + .Where(x => x.EventId == input.EventId && !x.DeleteMark && + ( + (x.PaymentMethod == 2 && x.PaymentStatus == 1) || + (x.PaymentMethod == 1 && (x.PaymentStatus == 1 || x.PaymentStatus == 0)) + ) && + x.PaymentStatus != 4 && x.PaymentStatus != 5 && x.PaymentStatus != 6); + + if (excludeRegistrationId.HasValue) + { + validRegistrationQuery = validRegistrationQuery.Where(x => x.Id != excludeRegistrationId.Value); + } + + // 查询这些用户在这些组别中是否已有报名记录 + var existingRegistrations = await _db.Queryable() + .InnerJoin((p, i) => p.RegistrationItemId == i.Id && !i.DeleteMark) + .InnerJoin(validRegistrationQuery, (p, i, r) => p.RegistrationId == r.Id) + .Where((p, i, r) => + userIds.Contains(p.UserId) && + groupIds.Contains(i.EventProjectGroupId.Value) && + !p.DeleteMark) + .Select((p, i, r) => new + { + UserId = p.UserId, + GroupId = i.EventProjectGroupId + }) + .Distinct() + .ToListAsync(); + + // 构建已报名的哈希集合,用于快速查找 + var registeredSet = new HashSet<(long UserId, long GroupId)>(); + foreach (var reg in existingRegistrations) + { + if (reg.GroupId.HasValue) + { + registeredSet.Add((reg.UserId, reg.GroupId.Value)); + } + } + + // 检查是否有重复 + foreach (var pair in checkPairs) + { + if (registeredSet.Contains((pair.UserId, pair.GroupId))) + { + var eventProjectGroup = eventProjectGroups.FirstOrDefault(epg => epg.Id == pair.GroupId); + var eventProject = eventProjects.FirstOrDefault(ep => ep.Id == eventProjectGroup?.EventProjectId); + var project = projects.FirstOrDefault(p => p.Id == eventProject?.ProjectId); + var projectName = project?.Name ?? "未知项目"; + var groupName = eventProjectGroup?.GroupName ?? "未知组别"; _logger.LogWarning("人员已报名该项目组别,人员: {PersonName}, 项目: {ProjectName}, 组别: {GroupName}", - person.Name, projectName, eventProjectGroup.GroupName); + pair.UserName, projectName, groupName); result.HasLimit = true; result.LimitDetails.Add(CreateLimitDetail( 0, - person.Name, + pair.UserName, "PersonAlreadyRegistered", - $"人员已报名检测:人员\"{person.Name}\"已在项目\"{projectName}\"的组别\"{eventProjectGroup.GroupName}\"中报名过,不能重复报名,避免占用多个名额")); + $"人员已报名检测:人员\"{pair.UserName}\"已在项目\"{projectName}\"的组别\"{groupName}\"中报名过,不能重复报名,避免占用多个名额")); + return; } } } diff --git a/YD_Event.Application/Event/UserEventRegistrationService.cs b/YD_Event.Application/Event/UserEventRegistrationService.cs index 31df42e..23d27d2 100644 --- a/YD_Event.Application/Event/UserEventRegistrationService.cs +++ b/YD_Event.Application/Event/UserEventRegistrationService.cs @@ -1096,18 +1096,26 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient return result; } - var eventProjectGroup = await _eventProjectGroupRepository.GetByIdAsync(input.GroupId); - if (eventProjectGroup != null) + // 优化:合并 eventProjectGroup → eventProject → project 三次查询为一次 JOIN 查询 + var groupWithProject = await _db.Queryable() + .LeftJoin((g, ep) => g.EventProjectId == ep.Id) + .LeftJoin((g, ep, p) => ep.ProjectId == p.Id) + .Where((g, ep, p) => g.Id == input.GroupId && !g.DeleteMark) + .Select((g, ep, p) => new + { + Group = g, + ProjectType = p.ProjectType + }) + .FirstAsync(); + + if (groupWithProject?.Group != null) { - // 获取项目类型信息 - var eventProject = await _eventProjectRepository.GetByIdAsync(eventProjectGroup.EventProjectId); - var baseProject = eventProject != null ? await _projectRepository.GetByIdAsync(eventProject.ProjectId) : null; - var isTeamProject = baseProject?.IsTeamProject() ?? false; + var isTeamProject = groupWithProject.ProjectType.IsTeamProject(); var persons = BuildValidationPersons(members, input.MemberId); // 使用统一的验证方法进行验证 - var validationResult = _unifiedRegistrationService.ValidatePersonQualifications(persons, eventProjectGroup, isTeamProject); + var validationResult = _unifiedRegistrationService.ValidatePersonQualifications(persons, groupWithProject.Group, isTeamProject); if (!validationResult.IsValid) { @@ -1352,13 +1360,27 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient // 获取所有赛事项目,用于关联 ProjectId var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == eventId); + // 优化:一次查询统计所有组别的已报名队伍数,替代循环查询 + // 注意:与原逻辑保持一致,统计的是 registrationItem 的数量(不去重 RegistrationId) + var registrationItems = await _registrationItemRepository.AsQueryable() + .Where(x => x.EventProjectGroupId.HasValue && !x.DeleteMark) + .InnerJoin((i, r) => i.RegistrationId == r.Id && !r.DeleteMark && r.EventId == eventId) + .Select((i, r) => new + { + GroupId = i.EventProjectGroupId + }) + .ToListAsync(); + + // 构建组别 -> 已报名队伍数 的映射(与原逻辑一致,直接统计 item 数量) + var groupTeamCountMap = registrationItems + .Where(x => x.GroupId.HasValue) + .GroupBy(x => x.GroupId!.Value) + .ToDictionary(g => g.Key, g => g.Count()); + foreach (var eventProjectGroup in eventProjectGroups) { - var currentTeams = await _registrationItemRepository.AsQueryable() - .Where(x => x.EventProjectGroupId == eventProjectGroup.Id && !x.DeleteMark) - .Select(x => x.RegistrationId) - .Distinct() - .CountAsync(); + // 从映射中获取已报名队伍数 + var currentTeams = groupTeamCountMap.TryGetValue(eventProjectGroup.Id, out var count) ? count : 0; // 查找该组别的项目限报配置 var eventProject = eventProjects.FirstOrDefault(ep => ep.Id == eventProjectGroup.EventProjectId);