using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using YD_Event.Application.Event.Dtos;
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;
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 _projectRepository;
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 IIdGenerator _idGenerator;
private readonly ILogger _logger;
public EventScoreService(
ISqlSugarRepository personRepository,
ISqlSugarRepository resultRepository,
ISqlSugarRepository certificateRepository,
ISqlSugarRepository registrationRepository,
ISqlSugarRepository templateRepository,
ISqlSugarRepository projectRepository,
ISqlSugarRepository teamMemberRepository,
ISqlSugarRepository eventRepository,
ISqlSugarRepository pointsRuleRepository,
ISqlSugarRepository pointsRuleDetailRepository,
ISqlSugarRepository pointsRecordRepository,
ISqlSugarRepository appUserRepository,
IIdGenerator idGenerator,
ILogger logger) : base(resultRepository)
{
_personRepository = personRepository;
_resultRepository = resultRepository;
_certificateRepository = certificateRepository;
_registrationRepository = registrationRepository;
_templateRepository = templateRepository;
_projectRepository = projectRepository;
_teamMemberRepository = teamMemberRepository;
_eventRepository = eventRepository;
_pointsRuleRepository = pointsRuleRepository;
_pointsRuleDetailRepository = pointsRuleDetailRepository;
_pointsRecordRepository = pointsRecordRepository;
_appUserRepository = appUserRepository;
_idGenerator = idGenerator;
_logger = logger;
}
///
/// 运动员成绩列表
///
[DisplayName("运动员成绩列表")]
public async Task> PageAthlete(ScoreQueryInput input)
{
var pageResult = await _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))
.WhereIF(input.ProjectId.HasValue, p => p.ProjectId == input.ProjectId)
.WhereIF(!string.IsNullOrWhiteSpace(input.GroupName), p => p.GroupName == input.GroupName)
.WhereIF(!string.IsNullOrWhiteSpace(input.Name), p => p.Name.Contains(input.Name))
.WhereIF(!string.IsNullOrWhiteSpace(input.UnitName), (p, r) => r.TeamName.Contains(input.UnitName))
.WhereIF(input.Gender.HasValue, p => p.Gender == input.Gender.Value)
.Select((p, r, res, c) => new ScoreListOutput
{
Id = p.Id,
Name = p.Name,
UnitName = r.TeamName,
Gender = p.Gender.ToString(),
Score = res.Score,
Rank = res.Rank,
ResultId = res.Id,
CertificateId = c.Id
})
.ToPagedListAsync(input);
foreach (var item in pageResult.Rows)
{
item.RankText = ConvertRankToChinese(item.Rank);
}
return pageResult;
}
///
/// 教练/领队列表
///
///
/// 通过运动员所在的团队关联获取教练和领队人员信息
///
[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 = p.Gender.Description(),
IdCardNumber = p.IdCard,
Mobile = p.Phone,
CertificateId = 0 // 需要单独查询,此处先设为0
})
.ToPagedListAsync(input);
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 rows = stream.Query().ToList();
_logger.LogInformation("解析Excel完成,共 {RowCount} 条记录", rows.Count);
if (!rows.Any())
{
throw Oops.Bah("Excel文件中没有数据");
}
// 验证导入数据
var validationResult = await ValidateImportDataAsync(rows, eventId);
if (!validationResult.IsValid)
{
var errorMessage = string.Join("\n", validationResult.Errors);
throw Oops.Bah($"数据验证失败:\n{errorMessage}");
}
// 步骤1:批量预加载该赛事的所有人员数据
var allPersons = await _personRepository.AsQueryable()
.InnerJoin((p, r) => p.RegistrationId == r.Id)
.Where((p, r) => r.EventId == eventId)
.Select((p, r) => new
{
p.Id,
p.Name,
p.ProjectName,
p.GroupName,
p.RegistrationId,
p.ProjectId
})
.ToListAsync();
_logger.LogInformation("预加载人员数据完成,共 {PersonCount} 人", allPersons.Count);
// 步骤2:使用字典建立人员索引(Name_ProjectName_GroupName -> Person)
var personDict = allPersons
.GroupBy(p => $"{p.Name}_{p.ProjectName}_{p.GroupName}")
.ToDictionary(g => g.Key, g => g.First());
// 预加载项目信息,用于获取项目类型名称
var projectIds = allPersons.Select(p => p.ProjectId).Distinct().ToList();
var projects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id));
var projectDict = projects.ToDictionary(p => p.Id, p => p.ProjectType.Description());
// 步骤3:预加载该赛事的所有成绩记录
var personIds = allPersons.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);
// 步骤4:批量处理数据,收集需要插入/更新的记录
var resultsToInsert = new List();
var resultsToUpdate = new List();
var now = DateTime.Now;
var notFoundPersons = new List();
foreach(var row in validationResult.ValidRows)
{
// 使用内存字典查找,避免数据库查询
var key = $"{row.Name}_{row.ProjectName}_{row.GroupName}";
if (!personDict.TryGetValue(key, out var person))
{
notFoundPersons.Add($"姓名:{row.Name}, 项目:{row.ProjectName}, 组别:{row.GroupName}");
continue;
}
if (resultDict.TryGetValue(person.Id, out var existingResult))
{
// 更新现有记录
existingResult.Score = row.Score;
existingResult.Rank = row.Rank;
existingResult.Remark = row.RankText;
existingResult.ProjectTypeName = projectDict.TryGetValue(person.ProjectId, out var projectTypeName) ? projectTypeName : string.Empty;
existingResult.UpdatedTime = now;
resultsToUpdate.Add(existingResult);
}
else
{
// 插入新记录
var newResult = new EventResult
{
Id = _idGenerator.NewLong(),
EventId = eventId,
RegistrationPersonId = person.Id,
RegistrationId = person.RegistrationId,
ProjectId = person.ProjectId,
ParticipantName = person.Name,
GroupName = person.GroupName,
ProjectTypeName = projectDict.TryGetValue(person.ProjectId, out var projectTypeName) ? projectTypeName : string.Empty,
Score = row.Score,
Rank = row.Rank,
Remark = row.RankText,
CreatedTime = now,
UpdatedTime = now
};
resultsToInsert.Add(newResult);
}
}
if (notFoundPersons.Any())
{
_logger.LogWarning("未找到匹配的参赛人员:{Count} 条", notFoundPersons.Count);
}
_logger.LogInformation("数据处理完成:新增 {InsertCount} 条,更新 {UpdateCount} 条,未找到 {NotFoundCount} 条",
resultsToInsert.Count, resultsToUpdate.Count, notFoundPersons.Count);
// 步骤5:批量操作数据库
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;
// 如果指定了模板,验证模板是否存在
CertificateTemplate? template = null;
if (input.TemplateId.HasValue)
{
template = await _templateRepository.GetFirstAsync(x =>
x.Id == input.TemplateId.Value &&
x.Status == AvailabilityStatus.Enable &&
!x.DeleteMark);
if (template == null)
{
throw Oops.Bah("指定的证书模板不存在或不可用");
}
}
// 批量预加载所有需要的数据
var persons = await _personRepository.GetListAsync(x => input.PersonIds.Contains(x.Id));
var personDict = persons.ToDictionary(p => p.Id);
var personIds = persons.Select(p => p.Id).ToList();
var existingResults = await _resultRepository.GetListAsync(x => personIds.Contains(x.RegistrationPersonId));
var resultDict = existingResults.ToDictionary(r => r.RegistrationPersonId);
var existingCerts = await _certificateRepository.GetListAsync(x =>
personIds.Contains(x.ParticipationRecordId));
var certDict = existingCerts.ToDictionary(c => c.ParticipationRecordId);
var certsToInsert = new List();
foreach (var personId in input.PersonIds)
{
if (!personDict.TryGetValue(personId, out var person))
{
result.FailCount++;
result.FailDetails.Add($"人员ID {personId} 不存在");
continue;
}
EventCertificate? cert = null;
if (person.PersonType == PersonType.Athlete || person.PersonType == PersonType.User)
{
if (!resultDict.TryGetValue(person.Id, out var resultRecord))
{
result.FailCount++;
result.FailDetails.Add($"人员 {person.Name} 没有成绩记录");
continue;
}
if (certDict.TryGetValue(person.Id, out var existingCert))
{
cert = existingCert;
}
else
{
cert = new EventCertificate
{
Id = _idGenerator.NewLong(),
EventId = input.EventId,
ResultId = resultRecord.Id,
RegistrationId = person.RegistrationId,
ParticipationRecordId = person.Id,
ParticipantName = person.Name,
GroupName = person.GroupName,
CertificateStatus = 1,
GeneratedTime = now,
CertificateNo = $"C{now:yyyyMMdd}{person.Id}",
CreatedTime = now,
TemplateId = input.TemplateId
};
certsToInsert.Add(cert);
}
}
else
{
if (certDict.TryGetValue(person.Id, out var existingCert))
{
cert = existingCert;
}
else
{
cert = new EventCertificate
{
Id = _idGenerator.NewLong(),
EventId = input.EventId,
RegistrationId = person.RegistrationId,
ParticipationRecordId = person.Id,
ParticipantName = person.Name,
GroupName = person.GroupName,
CertificateStatus = 1,
GeneratedTime = now,
CertificateNo = $"CC{now:yyyyMMdd}{person.Id}",
CreatedTime = now,
TemplateId = input.TemplateId
};
certsToInsert.Add(cert);
}
}
// 更新证书状态为已发放
cert.CertificateStatus = 2;
cert.IssuedTime = now;
cert.UpdatedTime = now;
result.SuccessCount++;
}
if (certsToInsert.Any())
{
await _certificateRepository.InsertRangeAsync(certsToInsert);
}
// 更新已有证书的发放状态
var existingCertsToUpdate = existingCerts.Where(c =>
input.PersonIds.Contains(c.ParticipationRecordId)).ToList();
if (existingCertsToUpdate.Any())
{
await _certificateRepository.UpdateRangeAsync(existingCertsToUpdate);
}
_logger.LogInformation("发放证书完成:成功 {SuccessCount} 条,失败 {FailCount} 条",
result.SuccessCount, result.FailCount);
return result;
}
///
/// 查看证书接口
///
/// 输入参数
/// 证书详情
[DisplayName("查看证书")]
public async Task