using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using YD_Event.Application.Event.Dtos;
using YD_Event.Application.Certificate;
using YD_Event.Core.Entities;
using YD_Event.Core.Enum;
using YD_Event.Core.Domain.Export;
using MiniExcelLibs;
using SqlSugar;
using OfficeOpenXml;
using OfficeOpenXml.Style;
using System.Drawing;
using static SKIT.FlurlHttpClient.Wechat.TenpayV3.Models.DepositMarketingMemberCardOpenCardCodesResponse.Types;
using YD_Event.Application.AppUser.Dtos;
namespace YD_Event.Application.Event;
///
/// 成绩与证书管理
///
public class EventScoreService : BaseService, ITransient
{
private static readonly string[] ChineseNumbers = { "零", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十" };
///
/// 将数字名次转换为汉字形式
///
private static string ConvertRankToChinese(int rank)
{
if (rank <= 0)
return string.Empty;
if (rank <= 10)
return $"第{ChineseNumbers[rank]}名";
if (rank < 20)
return $"第十{ChineseNumbers[rank % 10]}名";
if (rank % 10 == 0)
return $"第{ChineseNumbers[rank / 10]}十名";
return $"第{ChineseNumbers[rank / 10]}十{ChineseNumbers[rank % 10]}名";
}
private readonly ISqlSugarRepository _personRepository;
private readonly ISqlSugarRepository _resultRepository;
private readonly ISqlSugarRepository _certificateRepository;
private readonly ISqlSugarRepository _registrationRepository;
private readonly ISqlSugarRepository _templateRepository;
private readonly ISqlSugarRepository _certificateMasterRepository;
private readonly ISqlSugarRepository _projectRepository;
private readonly ISqlSugarRepository _eventProjectRepository;
private readonly ISqlSugarRepository _teamMemberRepository;
private readonly ISqlSugarRepository _eventRepository;
private readonly ISqlSugarRepository _pointsRuleRepository;
private readonly ISqlSugarRepository _pointsRuleDetailRepository;
private readonly ISqlSugarRepository _pointsRecordRepository;
private readonly ISqlSugarRepository _appUserRepository;
private readonly CertificateGenerateService _certificateGenerateService;
private readonly IIdGenerator _idGenerator;
private readonly ILogger _logger;
public EventScoreService(
ISqlSugarRepository personRepository,
ISqlSugarRepository resultRepository,
ISqlSugarRepository certificateRepository,
ISqlSugarRepository registrationRepository,
ISqlSugarRepository templateRepository,
ISqlSugarRepository certificateMasterRepository,
ISqlSugarRepository projectRepository,
ISqlSugarRepository eventProjectRepository,
ISqlSugarRepository teamMemberRepository,
ISqlSugarRepository eventRepository,
ISqlSugarRepository pointsRuleRepository,
ISqlSugarRepository pointsRuleDetailRepository,
ISqlSugarRepository pointsRecordRepository,
ISqlSugarRepository appUserRepository,
CertificateGenerateService certificateGenerateService,
IIdGenerator idGenerator,
ILogger logger) : base(resultRepository)
{
_personRepository = personRepository;
_resultRepository = resultRepository;
_certificateRepository = certificateRepository;
_registrationRepository = registrationRepository;
_templateRepository = templateRepository;
_certificateMasterRepository = certificateMasterRepository;
_projectRepository = projectRepository;
_eventProjectRepository = eventProjectRepository;
_teamMemberRepository = teamMemberRepository;
_eventRepository = eventRepository;
_pointsRuleRepository = pointsRuleRepository;
_pointsRuleDetailRepository = pointsRuleDetailRepository;
_pointsRecordRepository = pointsRecordRepository;
_appUserRepository = appUserRepository;
_certificateGenerateService = certificateGenerateService;
_idGenerator = idGenerator;
_logger = logger;
}
///
/// 运动员成绩列表
///
[DisplayName("运动员成绩列表")]
public async Task> PageAthlete(ScoreQueryInput input)
{
_logger.LogInformation("开始查询运动员成绩列表,赛事ID={EventId}, 项目ID={ProjectId}, 赛事项目组别ID={EventProjectGroupId}, 组别名称={GroupName}",
input.EventId, input.ProjectId, input.EventProjectGroupId, input.GroupName);
try
{
// 步骤1:获取所有 EventProject 信息,建立 EventProjectId -> ProjectId 映射
var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == input.EventId);
var eventProjectDictToProjectIdMap = eventProjects.ToDictionary(x => x.Id, x => x.ProjectId);
// 获取所有相关的 Project 信息
var allProjectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList();
var projectsList = await _projectRepository.GetListAsync(x => allProjectIds.Contains(x.Id));
var projectDict = projectsList.ToDictionary(x => x.Id, x => x);
// 步骤2:查询基本数据
var baseQuery = _personRepository.AsQueryable()
.LeftJoin((p, r) => p.RegistrationId == r.Id)
.LeftJoin((p, r, res) => p.Id == res.RegistrationPersonId)
.LeftJoin((p, r, res, c) => res.Id == c.ResultId)
.Where((p, r, res, c) => r.EventId == input.EventId && (p.PersonType == PersonType.Athlete || p.PersonType == PersonType.User));
// 应用筛选条件
if (input.ProjectId.HasValue)
{
baseQuery = baseQuery.Where((p, r, res, c) => p.ProjectId == input.ProjectId.Value);
}
if (input.EventProjectGroupId.HasValue)
{
baseQuery = baseQuery.Where((p, r, res, c) => p.EventProjectGroupId == input.EventProjectGroupId.Value);
}
else if (!string.IsNullOrWhiteSpace(input.GroupName))
{
baseQuery = baseQuery.Where((p, r, res, c) => p.GroupName == input.GroupName);
}
if (!string.IsNullOrWhiteSpace(input.Name))
{
baseQuery = baseQuery.Where((p, r, res, c) => p.Name.Contains(input.Name));
}
if (!string.IsNullOrWhiteSpace(input.UnitName))
{
baseQuery = baseQuery.Where((p, r, res, c) => r.TeamName.Contains(input.UnitName));
}
if (input.Gender.HasValue)
{
baseQuery = baseQuery.Where((p, r, res, c) => p.Gender == input.Gender.Value);
}
// 查询基本数据到内存
var baseData = await baseQuery
.OrderBy((p, r, res, c) => res.Rank)
.Select((p, r, res, c) => new
{
Id = p.Id,
Name = p.Name,
UnitName = r.TeamName ?? string.Empty,
Gender = (int)p.Gender,
Score = res.Score,
Rank = res.Rank,
ResultId = res.Id,
CertificateId = c.Id,
EventProjectId = p.ProjectId, // 注意:这里 p.ProjectId 实际上是 EventProject 的 Id
GroupName = p.GroupName ?? string.Empty,
TeamName = r.TeamName ?? string.Empty,
RegistrationItemId = p.RegistrationItemId // 增加报名项目唯一码,用于分组
})
.ToListAsync();
// 步骤3:进行分组处理(与赛事编排页面保持一致)
var processedData = new List();
// 先按 EventProjectId 和 GroupName 分组
var eventProjectGroups = baseData.GroupBy(x => new { x.EventProjectId, x.GroupName });
foreach (var eventProjectGroup in eventProjectGroups)
{
var firstItem = eventProjectGroup.First();
var isTeamProject = false;
// 查找对应的 Project 信息
if (eventProjectDictToProjectIdMap.TryGetValue(firstItem.EventProjectId, out var projectId) &&
projectDict.TryGetValue(projectId, out var project))
{
isTeamProject = project.ProjectType == ProjectTypeEnum.TeamSpeed ||
project.ProjectType == ProjectTypeEnum.TeamFreestyle;
}
if (isTeamProject)
{
// 团队项目:按 GroupName、TeamName 和 RegistrationItemId 分组(与赛事编排页面一致)
// 增加 RegistrationItemId 作为分组条件,确保同一条报名记录的多个小组被正确区分
var teamGroups = eventProjectGroup.GroupBy(x => new { x.GroupName, x.TeamName, x.RegistrationItemId });
foreach (var teamGroup in teamGroups)
{
var teamItems = teamGroup.ToList();
var names = string.Join(", ", teamItems.Select(x => x.Name).Distinct().OrderBy(x => x));
var representative = teamItems.OrderBy(x => x.Rank).First();
processedData.Add(new ScoreListOutput
{
Id = representative.Id,
Name = names,
UnitName = representative.UnitName,
Gender = representative.Gender,
Score = representative.Score,
Rank = representative.Rank,
ResultId = representative.ResultId,
CertificateId = representative.CertificateId,
RankText = ConvertRankToChinese(representative.Rank)
});
}
}
else
{
// 个人项目:直接添加
foreach (var item in eventProjectGroup)
{
processedData.Add(new ScoreListOutput
{
Id = item.Id,
Name = item.Name,
UnitName = item.UnitName,
Gender = item.Gender,
Score = item.Score,
Rank = item.Rank,
ResultId = item.ResultId,
CertificateId = item.CertificateId,
RankText = ConvertRankToChinese(item.Rank)
});
}
}
}
// 步骤4:手动进行分页
var total = processedData.Count;
var pageSize = input.PageSize > 0 ? input.PageSize : 10;
var pageNo = input.PageNo > 0 ? input.PageNo - 1 : 0;
var pagedData = processedData.Skip(pageNo * pageSize).Take(pageSize).ToList();
var pageResult = new PageResult
{
PageNo = input.PageNo,
PageSize = pageSize,
Total = total,
Pages = (int)Math.Ceiling((double)total / pageSize),
Rows = pagedData
};
_logger.LogInformation("查询运动员成绩列表完成,共{Count}条记录", pageResult.Total);
return pageResult;
}
catch (Exception ex)
{
_logger.LogError(ex, "查询运动员成绩列表失败");
throw;
}
}
///
/// 教练/领队列表
///
///
/// 通过运动员所在的团队关联获取教练和领队人员信息
///
[DisplayName("教练领队列表")]
public async Task> PageCoachLeader(CoachLeaderQueryInput input)
{
// 步骤1:获取该赛事的所有运动员的 UserId
var athleteUserIds = await _personRepository.AsQueryable()
.InnerJoin((p, r) => p.RegistrationId == r.Id)
.Where((p, r) => r.EventId == input.EventId && p.PersonType == PersonType.Athlete)
.Select(p => p.UserId)
.ToListAsync();
if (!athleteUserIds.Any())
{
return new PageResult();
}
// 步骤2:通过运动员的 UserId 找到对应的 TeamId
var teamIds = await _teamMemberRepository.AsQueryable()
.Where(tm => athleteUserIds.Contains(tm.UserId) && !tm.DeleteMark)
.Select(tm => tm.TeamId)
.Distinct()
.ToListAsync();
if (!teamIds.Any())
{
return new PageResult();
}
// 步骤3:通过 TeamId 获取同团队的教练/领队的 UserId
var coachLeaderUserIds = await _teamMemberRepository.AsQueryable()
.Where(tm => teamIds.Contains(tm.TeamId) && tm.PersonType == input.PersonType && !tm.DeleteMark)
.Select(tm => tm.UserId)
.Distinct()
.ToListAsync();
if (!coachLeaderUserIds.Any())
{
return new PageResult();
}
// 步骤4:从 AppUser 表获取教练/领队的详细信息(需要关联查询)
var userQuery = _appUserRepository.AsQueryable()
.Where(p => coachLeaderUserIds.Contains(p.Id))
.Where(p=> p.PersonType == input.PersonType);
var pageResult = await userQuery
.Select(p => new CoachLeaderListOutput
{
Id = p.Id,
Name = p.Name,
Age = p.Age,
Gender = (int)p.Gender,
BirthDate = p.BirthDate,
IdCardNumber = p.IdCardNumber,
IdCardImageUrl = p.IdCardImageUrl,
IdCardBackImageUrl = p.IdCardBackImageUrl,
Mobile = p.Mobile,
CertificateId = 0 // 需要单独查询,此处先设为0
})
.ToPagedListAsync(input);
foreach (var item in pageResult.Rows)
{
if (item.Age.HasValue)
{
continue;
}
if (item.BirthDate.HasValue)
{
item.Age = MemberOutput.CalculateAgeFromBirthDate(item.BirthDate);
}
}
return pageResult;
}
///
/// 导入成绩(性能优化版)
///
///
/// 性能优化策略:
/// 1. 批量预加载所有人员数据到内存
/// 2. 使用字典建立索引,避免循环内数据库查询
/// 3. 批量收集需要插入/更新的记录
/// 4. 最后一次性执行批量操作
/// 性能指标:导入10000条成绩数据时,响应时间 < 30秒,内存占用 < 512MB
/// 团队项目支持:团队项目按团队维度导入,一个团队对应一个完整证书
///
[UnitOfWork]
[DisplayName("导入成绩")]
public async Task ImportScore(long eventId, IFormFile file)
{
_logger.LogInformation("开始导入成绩:赛事ID={EventId}", eventId);
var startTime = DateTime.Now;
using var stream = file.OpenReadStream();
var allRows = stream.Query().ToList();
_logger.LogInformation("解析Excel完成,共 {RowCount} 条原始记录", allRows.Count);
// 过滤掉空行(没有报名ID的行)
var rows = allRows.Where(r =>
r.RegistrationItemId.HasValue &&
r.RegistrationItemId.Value > 0
).ToList();
_logger.LogInformation("过滤空行后,共 {RowCount} 条有效记录", rows.Count);
if (!rows.Any())
{
throw Oops.Bah("Excel文件中没有有效数据,请确保填写了报名ID");
}
// 验证导入数据
var validationResult = await ValidateImportDataAsync(rows, eventId);
if (!validationResult.IsValid)
{
var errorMessage = string.Join("\n", validationResult.Errors);
throw Oops.Bah($"数据验证失败:\n{errorMessage}");
}
// 步骤1:获取赛事的所有 EventProject 信息
var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == eventId);
var eventProjectDictToProjectId = eventProjects.ToDictionary(x => x.Id, x => x.ProjectId);
// 步骤2:批量预加载该赛事的所有人员数据(包含团队信息)
var allPersonsWithTeam = await _personRepository.AsQueryable()
.InnerJoin((p, r) => p.RegistrationId == r.Id)
.Where((p, r) => r.EventId == eventId)
.Select((p, r) => new {
p.Id,
p.RegistrationItemId,
p.Name,
p.ProjectName,
p.GroupName,
p.RegistrationId,
p.ProjectId,
r.TeamName
})
.ToListAsync();
_logger.LogInformation("预加载人员数据完成,共 {PersonCount} 人", allPersonsWithTeam.Count);
// 步骤3:获取所有相关项目信息,用于判断项目类型
var allProjectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList();
var allProjects = await _projectRepository.GetListAsync(x => allProjectIds.Contains(x.Id));
var allProjectDict = allProjects.ToDictionary(p => p.Id, p => p);
// 步骤4:使用字典建立人员索引(按RegistrationItemId索引,取第一个人作为代表)
// 注意:团队项目中一个 RegistrationItemId 可能对应多个运动员,但只需要第一个作为代表
var personByItemIdDict = allPersonsWithTeam
.GroupBy(p => p.RegistrationItemId)
.ToDictionary(g => g.Key, g => g.First());
// 预加载 EventRegistration 信息,用于获取归属单位 ID
var registrationIdsList = allPersonsWithTeam.Select(p => p.RegistrationId).Distinct().ToList();
var registrations = await _registrationRepository.GetListAsync(r => registrationIdsList.Contains(r.Id));
var registrationDict = registrations.ToDictionary(r => r.Id);
// 步骤5:预加载该赛事的所有成绩记录
var personIds = allPersonsWithTeam.Select(p => p.Id).ToList();
var existingResults = await _resultRepository.GetListAsync(x =>
x.EventId == eventId && personIds.Contains(x.RegistrationPersonId));
var resultDict = existingResults.ToDictionary(r => r.RegistrationPersonId);
// 步骤6:批量处理数据,收集需要插入/更新的记录
var resultsToInsert = new List();
var resultsToUpdate = new List();
var now = DateTime.Now;
var notFoundPersons = new List();
foreach(var row in validationResult.ValidRows)
{
// 使用报名ID进行匹配
if (!personByItemIdDict.TryGetValue(row.RegistrationItemId.Value, out var person))
{
notFoundPersons.Add($"报名ID:{row.RegistrationItemId.Value}");
continue;
}
// 获取项目信息,判断是否为团队项目
bool isTeamProject = false;
string projectTypeName = string.Empty;
if (eventProjectDictToProjectId.TryGetValue(person.ProjectId, out var projectId) &&
allProjectDict.TryGetValue(projectId, out var project))
{
isTeamProject = project.ProjectType == ProjectTypeEnum.TeamSpeed ||
project.ProjectType == ProjectTypeEnum.TeamFreestyle;
projectTypeName = project.ProjectType.Description();
}
// 如果是团队项目,找到该团队的所有成员
List teamMembers = null;
string combinedNames = person.Name;
if (isTeamProject)
{
// 查找该报名项目的所有成员
teamMembers = allPersonsWithTeam
.Where(p => p.RegistrationItemId == person.RegistrationItemId)
.OrderBy(p => (string)p.Name)
.ToList();
// 收集所有姓名
var namesList = new List();
foreach (var member in teamMembers)
{
namesList.Add((string)member.Name);
}
combinedNames = string.Join(", ", namesList);
}
// 处理成绩记录
if (resultDict.TryGetValue(person.Id, out var existingResult))
{
// 更新现有记录
existingResult.Score = row.Score;
existingResult.Rank = row.Rank;
existingResult.Remark = row.RankText;
existingResult.ProjectTypeName = projectTypeName;
// 对于团队项目,更新 ParticipantName 为团队所有成员姓名
if (isTeamProject)
{
existingResult.ParticipantName = combinedNames;
}
existingResult.UpdatedTime = now;
resultsToUpdate.Add(existingResult);
}
else
{
// 从 EventRegistration 获取归属单位 ID
long? merchantCertId = null;
if (registrationDict.TryGetValue(person.RegistrationId, out var reg))
{
merchantCertId = reg.MerchantCertificationId;
}
// 插入新记录
var newResult = new EventResult
{
Id = _idGenerator.NewLong(),
EventId = eventId,
RegistrationPersonId = person.Id,
RegistrationId = person.RegistrationId,
ProjectId = person.ProjectId,
ParticipantName = isTeamProject ? combinedNames : person.Name,
GroupName = person.GroupName,
MerchantCertificationId = merchantCertId,
ProjectTypeName = projectTypeName,
Score = row.Score,
Rank = row.Rank,
Remark = row.RankText,
CreatedTime = now,
UpdatedTime = now
};
resultsToInsert.Add(newResult);
}
}
if (notFoundPersons.Any())
{
_logger.LogWarning("未找到匹配的参赛人员:{Count} 条,未找到的报名ID:{Ids}",
notFoundPersons.Count, string.Join(", ", notFoundPersons));
}
_logger.LogInformation("数据处理完成:新增 {InsertCount} 条,更新 {UpdateCount} 条,未找到 {NotFoundCount} 条",
resultsToInsert.Count, resultsToUpdate.Count, notFoundPersons.Count);
// 步骤7:批量操作数据库
if (resultsToInsert.Any())
{
await _resultRepository.InsertRangeAsync(resultsToInsert);
_logger.LogInformation("批量插入成绩记录完成,共 {Count} 条", resultsToInsert.Count);
}
if (resultsToUpdate.Any())
{
await _resultRepository.UpdateRangeAsync(resultsToUpdate);
_logger.LogInformation("批量更新成绩记录完成,共 {Count} 条", resultsToUpdate.Count);
}
var duration = (DateTime.Now - startTime).TotalSeconds;
_logger.LogInformation("导入成绩完成:赛事ID={EventId},总耗时={Duration}秒", eventId, duration);
}
///
/// 发放证书(支持模板选择和批量发放)
///
[UnitOfWork]
[DisplayName("发放证书")]
public async Task IssueCertificate(IssueCertificateInput input)
{
var result = new IssueCertificateResult();
var now = DateTime.Now;
// 验证证书是否存在并获取证书信息
var certificate = await _certificateMasterRepository.GetFirstAsync(x =>
x.Id == input.CertificateId &&
x.Status == AvailabilityStatus.Enable &&
!x.DeleteMark);
if (certificate == null)
{
throw Oops.Bah("指定的证书不存在或不可用");
}
// 根据证书获取对应的模板ID
var templateId = certificate.TemplateId;
// 验证模板是否存在
var template = await _templateRepository.GetFirstAsync(x =>
x.Id == templateId &&
x.Status == AvailabilityStatus.Enable &&
!x.DeleteMark);
if (template == null)
{
throw Oops.Bah("证书关联的模板不存在或不可用");
}
// 根据页面类型确定允许的人员类型
List allowedPersonTypes = GetAllowedPersonTypes(input.PageType);
_logger.LogInformation("发放证书 - 页面类型: {PageType}, 允许的人员类型: {AllowedTypes}",
input.PageType, string.Join(", ", allowedPersonTypes));
// 步骤1:获取赛事的所有 EventProject 信息
var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == input.EventId);
var eventProjectDictToProjectId = eventProjects.ToDictionary(x => x.Id, x => x.ProjectId);
// 步骤2:获取所有相关项目信息
var allProjectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList();
var allProjects = await _projectRepository.GetListAsync(x => allProjectIds.Contains(x.Id));
var allProjectDict = allProjects.ToDictionary(p => p.Id, p => p);
// 步骤3:批量预加载该赛事的所有人员数据(包含团队信息)
var allPersonsWithTeam = await _personRepository.AsQueryable()
.InnerJoin((p, r) => p.RegistrationId == r.Id)
.Where((p, r) => r.EventId == input.EventId && !p.DeleteMark)
.Select((p, r) => new {
p.Id,
p.PersonType,
p.UserId,
p.Name,
p.ProjectName,
p.GroupName,
p.RegistrationId,
p.ProjectId,
p.RegistrationItemId,
p.EventProjectGroupId,
p.DeleteMark,
r.TeamName,
r.MerchantCertificationId,
Person = p
})
.ToListAsync();
// 构建最终的人员字典
var personDict = new Dictionary();
// 根据页面类型区分人员信息获取逻辑
if (input.PageType == 2 || input.PageType == 3)
{
// 页面类型为教练(2)或领队(3)时,从团队关系数据中获取对应人员ID
_logger.LogInformation("页面类型为教练/领队,从团队关系数据中获取人员信息");
// 建立 UserId 到人员的映射
var userToPersonMap = allPersonsWithTeam
.Where(p => p.UserId > 0)
.GroupBy(p => p.UserId)
.ToDictionary(g => g.Key, g => g.First());
// 通过 UserId 查找对应的人员
foreach (var userId in input.PersonIds)
{
if (userToPersonMap.TryGetValue(userId, out var person))
{
personDict[userId] = person;
_logger.LogInformation("通过 UserId {UserId} 找到对应的人员: {PersonId}", userId, person.Id);
}
else
{
_logger.LogWarning("无法通过 UserId {UserId} 找到对应的人员", userId);
}
}
}
else
{
// 页面类型为运动员(1)或其他,直接获取人员ID
_logger.LogInformation("页面类型为运动员,直接获取人员信息");
// 直接从人员列表中查找
foreach (var person in allPersonsWithTeam.Where(p => input.PersonIds.Contains(p.Id)))
{
personDict[person.Id] = person;
_logger.LogInformation("直接获取人员: {PersonId}", person.Id);
}
}
// 使用最终找到的人员ID列表
var foundPersonIds = personDict.Values.Select(p => (long)p.Id).ToList();
var existingResults = await _resultRepository.GetListAsync(x => foundPersonIds.Contains(x.RegistrationPersonId));
var resultDict = existingResults.ToDictionary(r => r.RegistrationPersonId);
var existingCerts = await _certificateRepository.GetListAsync(x =>
foundPersonIds.Contains(x.ParticipationRecordId));
var certDict = existingCerts.ToDictionary(c => c.ParticipationRecordId);
var certsToInsert = new List();
var certsToUpdate = new List();
foreach (var personId in input.PersonIds)
{
if (!personDict.TryGetValue(personId, out var person))
{
result.FailCount++;
result.FailDetails.Add($"人员ID {personId} 不存在");
continue;
}
// 验证人员类型是否与当前页面类型匹配
if (!allowedPersonTypes.Contains(person.PersonType))
{
result.FailCount++;
result.FailDetails.Add($"人员 {person.Name} 的类型 ({person.PersonType}) 与当前页面类型不匹配");
continue;
}
// 获取项目信息,判断是否为团队项目
bool isTeamProject = false;
string combinedNames = person.Name;
if (person.PersonType == PersonType.Athlete || person.PersonType == PersonType.User)
{
long projectId;
Core.Entities.Project project = null;
if (eventProjectDictToProjectId.TryGetValue(person.ProjectId, out projectId) &&
allProjectDict.TryGetValue(projectId, out project))
{
isTeamProject = project.ProjectType == ProjectTypeEnum.TeamSpeed ||
project.ProjectType == ProjectTypeEnum.TeamFreestyle;
}
// 如果是团队项目,找到该团队的所有成员并合并姓名
if (isTeamProject)
{
// 回到原来的方法,但确保不会出错
var teamMembers = allPersonsWithTeam
.Where(p => p.RegistrationItemId == person.RegistrationItemId)
.OrderBy(p => (string)p.Name)
.ToList();
var namesList = new List();
foreach (var member in teamMembers)
{
namesList.Add((string)member.Name);
}
combinedNames = string.Join(", ", namesList);
}
}
// 获取实际的 EventRegistrationPerson ID(如果通过 UserId 映射过来的,需要使用实际的 Id)
var actualPersonId = person.Id;
var merchantCertId = person.MerchantCertificationId;
EventCertificate? cert = null;
bool isNewCert = false;
if (person.PersonType == PersonType.Athlete || person.PersonType == PersonType.User)
{
EventResult resultRecord;
if (!resultDict.TryGetValue(actualPersonId, out resultRecord))
{
result.FailCount++;
result.FailDetails.Add($"人员 {person.Name} 没有成绩记录");
continue;
}
EventCertificate existingCert;
if (certDict.TryGetValue(actualPersonId, out existingCert))
{
cert = existingCert;
}
else
{
cert = new EventCertificate
{
Id = _idGenerator.NewLong(),
EventId = input.EventId,
ResultId = resultRecord.Id,
RegistrationId = person.RegistrationId,
ParticipationRecordId = actualPersonId,
ProjectId = person.ProjectId,
UserId = person.UserId,
ParticipantName = combinedNames, // 团队项目显示所有成员姓名
GroupName = person.GroupName,
MerchantCertificationId = merchantCertId,
CertificateStatus = 1,
GeneratedTime = now,
CertificateNo = $"C{now:yyyyMMdd}{actualPersonId}",
CreatedTime = now,
TemplateId = templateId,
CertificateUrl = string.Empty
};
certsToInsert.Add(cert);
isNewCert = true;
}
// 构建占位符数据(传递合并后的姓名)
var placeholderData = BuildPlaceholderData(person.Person, resultRecord, combinedNames);
// 生成 PDF 证书
try
{
var generateResult = await _certificateGenerateService.GeneratePdf(
new YD_Event.Application.Certificate.Dtos.GeneratePdfCertificateInput
{
CertificateId = certificate.Id,
TemplateId = templateId,
Data = placeholderData,
GenerateQRCode = true,
QRCodeSize = 150
});
if (generateResult.IsSuccess && !string.IsNullOrEmpty(generateResult.PdfUrl))
{
cert.CertificateUrl = generateResult.PdfUrl;
cert.CertificateStatus = 1; // 已生成
cert.GeneratedTime = now;
cert.ParticipantName = combinedNames; // 更新证书上的姓名
_logger.LogInformation("PDF证书生成成功: EventCertificateId={Id}, Url={Url}", cert.Id, generateResult.PdfUrl);
}
else
{
_logger.LogWarning("PDF证书生成失败: {Message}", generateResult.ErrorMessage);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "PDF证书生成异常: EventCertificateId={Id}", cert.Id);
}
}
else
{
EventCertificate existingCert;
if (certDict.TryGetValue(actualPersonId, out existingCert))
{
cert = existingCert;
}
else
{
cert = new EventCertificate
{
Id = _idGenerator.NewLong(),
EventId = input.EventId,
RegistrationId = person.RegistrationId,
ParticipationRecordId = actualPersonId,
ProjectId = person.ProjectId,
UserId = person.UserId,
ParticipantName = person.Name,
GroupName = person.GroupName,
MerchantCertificationId = merchantCertId,
CertificateStatus = 1,
GeneratedTime = now,
CertificateNo = $"CC{now:yyyyMMdd}{actualPersonId}",
CreatedTime = now,
TemplateId = templateId,
CertificateUrl = string.Empty
};
certsToInsert.Add(cert);
isNewCert = true;
}
// 构建占位符数据(教练/领队没有成绩记录)
var placeholderData = BuildPlaceholderDataForCoachLeader(person.Person);
// 生成 PDF 证书
try
{
var generateResult = await _certificateGenerateService.GeneratePdf(
new YD_Event.Application.Certificate.Dtos.GeneratePdfCertificateInput
{
CertificateId = certificate.Id,
TemplateId = templateId,
Data = placeholderData,
GenerateQRCode = true,
QRCodeSize = 150
});
if (generateResult.IsSuccess && !string.IsNullOrEmpty(generateResult.PdfUrl))
{
cert.CertificateUrl = generateResult.PdfUrl;
cert.CertificateStatus = 1; // 已生成
cert.GeneratedTime = now;
_logger.LogInformation("PDF证书生成成功: EventCertificateId={Id}, Url={Url}", cert.Id, generateResult.PdfUrl);
}
else
{
_logger.LogWarning("PDF证书生成失败: {Message}", generateResult.ErrorMessage);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "PDF证书生成异常: EventCertificateId={Id}", cert.Id);
}
}
// 更新证书状态为已发放
cert.CertificateStatus = 2; // 已发放
cert.IssuedTime = now;
cert.UpdatedTime = now;
// 确保 CertificateUrl 不为 null
if (string.IsNullOrEmpty(cert.CertificateUrl))
{
cert.CertificateUrl = string.Empty;
}
if (!isNewCert)
{
certsToUpdate.Add(cert);
}
result.SuccessCount++;
}
if (certsToInsert.Any())
{
await _certificateRepository.InsertRangeAsync(certsToInsert);
}
// 更新已有证书的发放状态
if (certsToUpdate.Any())
{
await _certificateRepository.UpdateRangeAsync(certsToUpdate);
}
_logger.LogInformation("发放证书完成:成功 {SuccessCount} 条,失败 {FailCount} 条",
result.SuccessCount, result.FailCount);
return result;
}
///
/// 根据页面类型获取允许的人员类型列表
///
/// 页面类型:1-运动员页面,2-教练页面,3-领队页面
/// 允许的人员类型列表
private List GetAllowedPersonTypes(int pageType)
{
return pageType switch
{
1 => new List { PersonType.Athlete, PersonType.User },
2 => new List { PersonType.Coach },
3 => new List { PersonType.Leader },
_ => new List { PersonType.Athlete, PersonType.User, PersonType.Coach, PersonType.Leader }
};
}
///
/// 构建运动员占位符数据
///
private Dictionary BuildPlaceholderData(EventRegistrationPerson person, EventResult result, string combinedNames = null)
{
var displayName = string.IsNullOrEmpty(combinedNames) ? person.Name : combinedNames;
var data = new Dictionary
{
["name"] = displayName,
["姓名"] = displayName,
//["group"] = person.GroupName,
//["组别"] = person.GroupName,
["项目组别"] = $"{person.ProjectName}-{person.GroupName}",
//["project"] = result.ProjectTypeName,
//["项目"] = result.ProjectTypeName,
["score"] = result.Score,
["成绩"] = result.Score,
["rank"] = result.Rank.ToString(),
["名次"] = GetRankText(result.Rank),
["danwei"] = person.ProjectName ?? "个人",
["单位"] = "上海跃动文化科技有限公司",
["参赛单位"] = "上海跃动文化科技有限公司"
};
// 添加日期信息
var now = DateTime.Now;
data["year"] = now.Year.ToString();
data["month"] = now.Month.ToString();
data["day"] = now.Day.ToString();
data["date"] = now.ToString("yyyy年MM月dd日");
_logger.LogDebug("构建占位符数据: {Data}", string.Join(", ", data.Select(kv => $"{kv.Key}={kv.Value}")));
return data;
}
///
/// 构建教练/领队占位符数据
///
private Dictionary BuildPlaceholderDataForCoachLeader(EventRegistrationPerson person)
{
var data = new Dictionary
{
["name"] = person.Name,
["姓名"] = person.Name,
["group"] = person.GroupName,
["组别"] = person.GroupName,
["项目组别"] = person.GroupName,
["danwei"] = person.ProjectName ?? "个人",
["单位"] = person.ProjectName ?? "个人",
["参赛单位"] = person.ProjectName ?? "个人"
};
// 添加日期信息
var now = DateTime.Now;
data["year"] = now.Year.ToString();
data["month"] = now.Month.ToString();
data["day"] = now.Day.ToString();
data["date"] = now.ToString("yyyy年MM月dd日");
_logger.LogDebug("构建教练/领队占位符数据: {Data}", string.Join(", ", data.Select(kv => $"{kv.Key}={kv.Value}")));
return data;
}
///
/// 获取名次文本
///
private string GetRankText(int rank)
{
if (rank <= 0) return "";
var chineseNumbers = new[] { "零", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十" };
if (rank <= 10)
{
return $"第{chineseNumbers[rank]}名";
}
else if (rank < 20)
{
return $"第十{chineseNumbers[rank % 10]}名";
}
else if (rank < 100)
{
var tens = rank / 10;
var ones = rank % 10;
if (ones == 0)
{
return $"第{chineseNumbers[tens]}十名";
}
return $"第{chineseNumbers[tens]}十{chineseNumbers[ones]}名";
}
return $"第{rank}名";
}
///
/// 查看证书接口
///
/// 输入参数
/// 证书详情
[DisplayName("查看证书")]
[HttpPost("getcertificatedetail")]
public async Task