824 lines
30 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
using static SKIT.FlurlHttpClient.Wechat.TenpayV3.Models.DepositMarketingMemberCardOpenCardCodesResponse.Types;
using YD_Event.Application.AppUser.Dtos;
namespace YD_Event.Application.Event;
/// <summary>
/// 成绩与证书管理
/// </summary>
public class EventScoreService : BaseService<EventResult>, ITransient
{
private static readonly string[] ChineseNumbers = { "零", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十" };
/// <summary>
/// 将数字名次转换为汉字形式
/// </summary>
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<EventRegistrationPerson> _personRepository;
private readonly ISqlSugarRepository<EventResult> _resultRepository;
private readonly ISqlSugarRepository<EventCertificate> _certificateRepository;
private readonly ISqlSugarRepository<EventRegistration> _registrationRepository;
private readonly ISqlSugarRepository<CertificateTemplate> _templateRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Project> _projectRepository;
private readonly ISqlSugarRepository<TeamMember> _teamMemberRepository;
private readonly ISqlSugarRepository<EventInfo> _eventRepository;
private readonly ISqlSugarRepository<PointsRule> _pointsRuleRepository;
private readonly ISqlSugarRepository<PointsRuleDetail> _pointsRuleDetailRepository;
private readonly ISqlSugarRepository<PointsRecord> _pointsRecordRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.AppUser> _appUserRepository;
private readonly IIdGenerator _idGenerator;
private readonly ILogger<EventScoreService> _logger;
public EventScoreService(
ISqlSugarRepository<EventRegistrationPerson> personRepository,
ISqlSugarRepository<EventResult> resultRepository,
ISqlSugarRepository<EventCertificate> certificateRepository,
ISqlSugarRepository<EventRegistration> registrationRepository,
ISqlSugarRepository<CertificateTemplate> templateRepository,
ISqlSugarRepository<YD_Event.Core.Entities.Project> projectRepository,
ISqlSugarRepository<TeamMember> teamMemberRepository,
ISqlSugarRepository<EventInfo> eventRepository,
ISqlSugarRepository<PointsRule> pointsRuleRepository,
ISqlSugarRepository<PointsRuleDetail> pointsRuleDetailRepository,
ISqlSugarRepository<PointsRecord> pointsRecordRepository,
ISqlSugarRepository<YD_Event.Core.Entities.AppUser> appUserRepository,
IIdGenerator idGenerator,
ILogger<EventScoreService> 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;
}
/// <summary>
/// 运动员成绩列表
/// </summary>
[DisplayName("运动员成绩列表")]
public async Task<PageResult<ScoreListOutput>> PageAthlete(ScoreQueryInput input)
{
var pageResult = await _personRepository.AsQueryable()
.LeftJoin<EventRegistration>((p, r) => p.RegistrationId == r.Id)
.LeftJoin<EventResult>((p, r, res) => p.Id == res.RegistrationPersonId)
.LeftJoin<EventCertificate>((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;
}
/// <summary>
/// 教练/领队列表
/// </summary>
/// <remarks>
/// 通过运动员所在的团队关联获取教练和领队人员信息
/// </remarks>
[DisplayName("教练领队列表")]
public async Task<PageResult<CoachLeaderListOutput>> PageCoachLeader(CoachLeaderQueryInput input)
{
// 步骤1:获取该赛事的所有运动员的 UserId
var athleteUserIds = await _personRepository.AsQueryable()
.InnerJoin<EventRegistration>((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<CoachLeaderListOutput>();
}
// 步骤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<CoachLeaderListOutput>();
}
// 步骤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<CoachLeaderListOutput>();
}
// 步骤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,
IdCardNumber = p.IdCardImageUrl,
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;
}
/// <summary>
/// 导入成绩(性能优化版)
/// </summary>
/// <remarks>
/// 性能优化策略:
/// 1. 批量预加载所有人员数据到内存
/// 2. 使用字典建立索引,避免循环内数据库查询
/// 3. 批量收集需要插入/更新的记录
/// 4. 最后一次性执行批量操作
/// 性能指标:导入10000条成绩数据时,响应时间 < 30秒,内存占用 < 512MB
/// </remarks>
[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<ScoreImportDto>().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<EventRegistration>((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<EventResult>();
var resultsToUpdate = new List<EventResult>();
var now = DateTime.Now;
var notFoundPersons = new List<string>();
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);
}
/// <summary>
/// 发放证书(支持模板选择和批量发放)
/// </summary>
[UnitOfWork]
[DisplayName("发放证书")]
public async Task<IssueCertificateResult> 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<EventCertificate>();
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;
}
/// <summary>
/// 查看证书接口
/// </summary>
/// <param name="input">输入参数</param>
/// <returns>证书详情</returns>
[DisplayName("查看证书")]
public async Task<object> GetCertificateDetail(YD_Event.Application.Certificate.Dtos.IdInput input)
{
if (input.Id <= 0)
{
throw Oops.Bah("无效的 ID");
}
var personExists = await _personRepository.IsAnyAsync(x => x.Id == input.Id);
if (!personExists)
{
throw Oops.Bah("人员记录不存在");
}
var cert = await _certificateRepository.GetFirstAsync(x => x.ParticipationRecordId == input.Id);
if (cert == null)
{
throw Oops.Bah("未找到对应的证书信息");
}
return new { Url = cert.CertificateUrl };
}
/// <summary>
/// 验证证书真伪
/// </summary>
[DisplayName("验证证书真伪")]
public async Task<object> VerifyCertificate(string certificateNo)
{
if (string.IsNullOrWhiteSpace(certificateNo))
{
throw Oops.Bah("证书编号不能为空");
}
var cert = await _certificateRepository.GetFirstAsync(x =>
x.CertificateNo == certificateNo && !x.DeleteMark);
if (cert == null)
{
return new
{
IsValid = false,
Message = "证书不存在"
};
}
return new
{
IsValid = true,
CertificateNo = cert.CertificateNo,
ParticipantName = cert.ParticipantName,
GroupName = cert.GroupName,
IssuedTime = cert.IssuedTime,
CertificateStatus = cert.CertificateStatus
};
}
/// <summary>
/// 下载模板
/// </summary>
[DisplayName("下载模板")]
[HttpGet]
public IActionResult DownloadTemplate()
{
var exporter = new ScoreTemplateExporter();
var bytes = exporter.ExportAsync().GetAwaiter().GetResult();
return new FileContentResult(bytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
{
FileDownloadName = "成绩导入模板.xlsx"
};
}
/// <summary>
/// 成绩模板导出器
/// </summary>
private class ScoreTemplateExporter : ExcelExportBase
{
public override async Task<byte[]> ExportAsync()
{
CreateWorksheet("成绩导入模板");
var headers = new string[] {
"赛事地址",
"项目",
"组别",
"姓名",
"联系方式",
"单位",
"成绩",
"名次(汉字)",
"名次(数字)"
};
var headerColor = Color.FromArgb(204, 255, 204);
SetHeaders(headers, 1, headerColor, 11);
var widths = new double[] { 15, 15, 15, 12, 15, 20, 15, 15, 15 };
SetColumnWidths(widths);
SetRowHeight(1, 25);
return await GetAsByteArrayAsync();
}
}
/// <summary>
/// 导出证书
/// </summary>
[DisplayName("导出证书")]
public async Task<string> ExportCertificate(ScoreQueryInput input)
{
var list = await _certificateRepository.AsQueryable()
.LeftJoin<EventResult>((c, r) => c.ResultId == r.Id)
.LeftJoin<EventRegistration>((c, r, reg) => c.RegistrationId == reg.Id)
.LeftJoin<YD_Event.Core.Entities.Project>((c, r, reg, p) => c.ProjectId == p.Id)
.Where((c, r, reg, p) => c.EventId == input.EventId)
.WhereIF(input.ProjectId.HasValue, (c, r, reg, p) => c.ProjectId == input.ProjectId)
.WhereIF(!string.IsNullOrWhiteSpace(input.GroupName), (c, r, reg, p) => c.GroupName == input.GroupName)
.WhereIF(!string.IsNullOrWhiteSpace(input.Name), (c, r, reg, p) => c.ParticipantName.Contains(input.Name))
.Select((c, r, reg, p) => new CertificateExportDto
{
CertificateNo = c.CertificateNo,
ParticipantName = c.ParticipantName,
ProjectName = p.Name,
GroupName = c.GroupName,
UnitName = reg.TeamName,
Score = r.Score,
Rank = r.Rank,
CertificateType = c.CertificateType == 1 ? "冠军证书" :
c.CertificateType == 2 ? "亚军证书" :
c.CertificateType == 3 ? "季军证书" :
c.CertificateType == 4 ? "优胜证书" : "参与证书",
IssuedTime = c.IssuedTime
})
.ToListAsync();
using var stream = new MemoryStream();
stream.SaveAs(list);
return Convert.ToBase64String(stream.ToArray());
}
/// <summary>
/// 导入成绩验证结果
/// </summary>
public class ImportScoreValidationResult
{
public bool IsValid { get; set; }
public List<string> Errors { get; set; } = new List<string>();
public List<ScoreImportDto> ValidRows { get; set; } = new List<ScoreImportDto>();
}
/// <summary>
/// 验证导入数据
/// </summary>
private async Task<ImportScoreValidationResult> ValidateImportDataAsync(List<ScoreImportDto> rows, long eventId)
{
var result = new ImportScoreValidationResult();
var errors = new List<string>();
for (int i = 0; i < rows.Count; i++)
{
var row = rows[i];
var rowErrors = new List<string>();
var rowNumber = i + 2;
if (string.IsNullOrWhiteSpace(row.Name))
rowErrors.Add("姓名不能为空");
if (string.IsNullOrWhiteSpace(row.ProjectName))
rowErrors.Add("项目不能为空");
if (string.IsNullOrWhiteSpace(row.GroupName))
rowErrors.Add("组别不能为空");
if (string.IsNullOrWhiteSpace(row.Score))
rowErrors.Add("成绩不能为空");
if (row.Rank <= 0)
rowErrors.Add("名次必须为正整数");
if (rowErrors.Any())
{
errors.Add($"第 {rowNumber} 行:{string.Join(",", rowErrors)}");
}
else
{
result.ValidRows.Add(row);
}
}
if (errors.Any())
{
result.IsValid = false;
result.Errors = errors;
}
else
{
result.IsValid = true;
}
return result;
}
/// <summary>
/// 发放积分输入模型
/// </summary>
public class IssuePointsInput
{
public long EventId { get; set; }
}
/// <summary>
/// 发放积分
/// </summary>
[DisplayName("发放积分")]
[HttpPost("issuePoints")]
[UnitOfWork]
public async Task<object> IssuePoints([FromBody] IssuePointsInput input)
{
_logger.LogInformation("开始发放积分,赛事ID={EventId}", input.EventId);
var eventInfo = await _eventRepository.GetByIdAsync(input.EventId);
if (eventInfo == null)
{
throw Oops.Bah("赛事不存在");
}
if (!eventInfo.PointsRuleId.HasValue)
{
throw Oops.Bah("该赛事未配置积分规则");
}
var pointsRule = await _pointsRuleRepository.GetByIdAsync(eventInfo.PointsRuleId.Value);
if (pointsRule == null)
{
throw Oops.Bah("积分规则不存在");
}
if (pointsRule.Status != AvailabilityStatus.Enable)
{
throw Oops.Bah("积分规则未启用");
}
var ruleDetails = await _pointsRuleDetailRepository.AsQueryable()
.Where(x => x.PointsRuleId == pointsRule.Id)
.OrderBy(x => x.StartRank)
.ToListAsync();
var results = await _resultRepository.AsQueryable()
.InnerJoin<EventRegistrationPerson>((r, p) => r.RegistrationPersonId == p.Id)
.Where((r, p) => r.EventId == input.EventId && r.Rank > 0)
.Select((r, p) => new
{
ResultId = r.Id,
UserId = p.UserId,
ProjectId = r.ProjectId,
GroupName = r.GroupName,
Rank = r.Rank,
ParticipantName = r.ParticipantName,
ProjectIdForRecord = r.ProjectId
})
.ToListAsync();
if (!results.Any())
{
throw Oops.Bah("没有可发放积分的成绩记录");
}
var processedResultIdsList = await _pointsRecordRepository.AsQueryable()
.Where(x => x.EventId == input.EventId && x.ResultId.HasValue)
.Select(x => x.ResultId.Value)
.ToListAsync();
var processedResultIds = new HashSet<long>(processedResultIdsList);
int successCount = 0;
int skipCount = 0;
var pointsRecords = new List<PointsRecord>();
foreach (var result in results)
{
if (processedResultIds.Contains(result.ResultId))
{
skipCount++;
continue;
}
int pointsToAward = 0;
if (pointsRule.ParticipationPoints.HasValue && pointsRule.ParticipationPoints.Value > 0)
{
pointsToAward += pointsRule.ParticipationPoints.Value;
}
var matchingDetail = ruleDetails.FirstOrDefault(d => result.Rank >= d.StartRank && result.Rank <= d.EndRank);
if (matchingDetail != null)
{
pointsToAward += matchingDetail.Points;
}
if (pointsToAward > 0)
{
var record = new PointsRecord
{
Id = _idGenerator.NextId(),
UserId = result.UserId,
Type = PointsOperationType.Acquire,
EventId = input.EventId,
ProjectId = result.ProjectIdForRecord,
ResultId = result.ResultId,
Points = pointsToAward,
Remark = eventInfo.EventName,
Status = AvailabilityStatus.Enable,
CreatedTime = DateTime.Now
};
pointsRecords.Add(record);
successCount++;
}
}
if (pointsRecords.Any())
{
await _pointsRecordRepository.InsertRangeAsync(pointsRecords);
}
_logger.LogInformation("积分发放完成:赛事ID={EventId},成功发放={SuccessCount},跳过={SkipCount}",
input.EventId, successCount, skipCount);
return new
{
Success = true,
Message = $"积分发放成功!已发放 {successCount} 条积分记录,跳过 {skipCount} 条已发放记录"
};
}
}