using YD_Event.Application.Event.Dtos;
using YD_Event.Core.Entities;
using YD_Event.Core.Enum;
using SqlSugar;
using Mapster;
using YD_Event.Application.Auth;
using Furion.DynamicApiController;
using Microsoft.AspNetCore.Mvc;
using YD_Event.Application.File;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Authorization;
using System.ComponentModel;
namespace YD_Event.Application.Event;
///
/// 用户端赛事报名服务
///
public class UserCompetitionService : IDynamicApiController, ITransient
{
private readonly ISqlSugarRepository _eventRepository;
private readonly ISqlSugarRepository _unitRepository;
private readonly ISqlSugarRepository _projectRepository;
private readonly ISqlSugarRepository _registrationRepository;
private readonly ISqlSugarRepository _registrationItemRepository;
private readonly ISqlSugarRepository _registrationPersonRepository;
private readonly ISqlSugarRepository _userRepository;
private readonly ISqlSugarRepository _feeConfigRepository;
private readonly ISqlSugarRepository _limitRepository;
private readonly ISqlSugarRepository _pointsRuleRepository;
private readonly ISqlSugarRepository _paymentRepository;
private readonly ISqlSugarRepository _certificateRepository;
private readonly AuthManager _authManager;
private readonly IIdGenerator _idGenerator;
private readonly FileService _fileService;
public UserCompetitionService(
ISqlSugarRepository eventRepository,
ISqlSugarRepository unitRepository,
ISqlSugarRepository projectRepository,
ISqlSugarRepository registrationRepository,
ISqlSugarRepository registrationItemRepository,
ISqlSugarRepository registrationPersonRepository,
ISqlSugarRepository userRepository,
ISqlSugarRepository feeConfigRepository,
ISqlSugarRepository limitRepository,
ISqlSugarRepository pointsRuleRepository,
ISqlSugarRepository paymentRepository,
ISqlSugarRepository certificateRepository,
AuthManager authManager,
IIdGenerator idGenerator,
FileService fileService)
{
_eventRepository = eventRepository;
_unitRepository = unitRepository;
_projectRepository = projectRepository;
_registrationRepository = registrationRepository;
_registrationItemRepository = registrationItemRepository;
_registrationPersonRepository = registrationPersonRepository;
_userRepository = userRepository;
_feeConfigRepository = feeConfigRepository;
_limitRepository = limitRepository;
_pointsRuleRepository = pointsRuleRepository;
_paymentRepository = paymentRepository;
_certificateRepository = certificateRepository;
_authManager = authManager;
_idGenerator = idGenerator;
_fileService = fileService;
}
///
/// 1. 赛事列表展示接口
///
[DisplayName("赛事列表展示")]
[HttpGet("list")]
public async Task> Page([FromQuery] UserEventQueryInput input)
{
var query = _eventRepository.AsQueryable()
.Where(x => x.Status == AvailabilityStatus.Enable)
.WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), x => x.EventName.Contains(input.Keyword))
.WhereIF(input.Status.HasValue && input.Status != 0, x => x.EventStatus == input.Status)
.WhereIF(input.Type.HasValue, x => x.EventType == input.Type)
.WhereIF(input.StartTime.HasValue, x => x.MatchStartTime >= input.StartTime)
.WhereIF(input.EndTime.HasValue, x => x.MatchEndTime <= input.EndTime)
.OrderByDescending(x => x.CreatedTime);
return await query.Select(x => new UserEventListOutput
{
Id = x.Id,
Name = x.EventName,
CoverImage = x.CoverImage,
Status = x.EventStatus.ToString(),
Type = x.EventType.ToString(),
RegistrationEndTime = x.RegistrationEndTime,
MatchStartTime = x.MatchStartTime,
MatchEndTime = x.MatchEndTime
}).ToPagedListAsync(input);
}
///
/// 2. 赛事详情查看接口
///
[DisplayName("赛事详情查看")]
[HttpGet("detail/{id}")]
public async Task Detail([FromRoute] long id)
{
var eventInfo = await _eventRepository.GetByIdAsync(id);
if (eventInfo == null) throw Oops.Bah("赛事不存在");
var output = eventInfo.Adapt();
output.Name = eventInfo.EventName;
output.Description = eventInfo.EventIntroduction;
output.Address = eventInfo.DetailAddress;
output.EventVideo = eventInfo.EventVideo;
output.RegistrationType = eventInfo.RegistrationType.ToString();
output.IsNeedMaterial = eventInfo.IsNeedMaterial;
output.MaterialRemark = eventInfo.MaterialRemark;
output.MaterialUploadEndTime = eventInfo.MaterialUploadEndTime;
if (eventInfo.PointsRuleId.HasValue)
{
var rule = await _pointsRuleRepository.GetByIdAsync(eventInfo.PointsRuleId.Value);
output.PointsRuleName = rule?.RuleName ?? "";
}
var units = await _unitRepository.GetListAsync(x => x.EventId == id);
output.Units = units.OrderBy(x => x.Sort).Select(x => x.Adapt()).ToList();
var limit = await _limitRepository.GetFirstAsync(x => x.EventId == id);
if (limit != null)
{
output.LimitConfig = new UserEventLimitOutput
{
IndividualProjectLimit = limit.IndividualProjectLimit,
TeamProjectLimit = limit.TeamProjectLimit
};
}
var fee = await _feeConfigRepository.GetFirstAsync(x => x.EventId == id);
if (fee != null)
{
output.FeeConfig = new UserEventFeeOutput
{
ChargeMode = fee.ChargeMode.ToString(),
Price = fee.Price
};
}
var projects = await _projectRepository.GetListAsync(x => x.EventId == id && x.Status == AvailabilityStatus.Enable);
output.Projects = projects.Select(p => new UserEventProjectListOutput
{
Id = p.Id,
Name = p.Name,
Fee = p.Fee,
CompletionCount = p.CompletionCount
}).ToList();
var userId = _authManager.UserId;
var registration = await _registrationRepository.GetFirstAsync(x => x.EventId == id && x.CreatedUserId == userId && !x.DeleteMark);
if (registration != null)
{
output.RegistrationId = registration.Id;
output.UserRegistrationStatus = GetRegistrationStatusDesc(registration.RegistrationStatus);
}
else
{
output.UserRegistrationStatus = "未报名";
}
return output;
}
///
/// 3.1 选择报名项目接口
///
[DisplayName("选择报名项目")]
[HttpGet("projects/{eventId}")]
public async Task> GetProjects([FromRoute] long eventId)
{
var projects = await _projectRepository.GetListAsync(x => x.EventId == eventId && x.Status == AvailabilityStatus.Enable);
return projects.Select(p => new UserEventProjectListOutput
{
Id = p.Id,
Name = p.Name,
Fee = p.Fee,
TeamSize = 1, // 默认为1
CompletionCount = p.CompletionCount
}).ToList();
}
///
/// 3.2 分配队员接口 - 搜索队员
///
[DisplayName("搜索队员")]
[HttpGet("members/search")]
public async Task> SearchMembers([FromQuery] string keyword)
{
if (string.IsNullOrWhiteSpace(keyword)) return new List();
var users = await _userRepository.AsQueryable()
.Where(x => (x.Mobile.Contains(keyword) || x.Id.ToString() == keyword) && !x.DeleteMark)
.Take(10)
.ToListAsync();
return users.Select(u => new UserTeamMemberOutput
{
UserId = u.Id,
Name = u.Name,
Mobile = u.Mobile,
IdCard = MaskIdCard(u.IdCardNumber),
Gender = u.Gender.ToString(),
IsEligible = true,
IneligibilityReason = ""
}).ToList();
}
private string MaskIdCard(string idCard)
{
if (string.IsNullOrEmpty(idCard) || idCard.Length < 10) return idCard;
return idCard.Substring(0, 6) + "********" + idCard.Substring(idCard.Length - 4);
}
///
/// 3.2 分配队员接口 - 提交报名(含保存草稿)
///
[DisplayName("提交报名")]
[HttpPost("submit")]
[UnitOfWork]
public async Task SubmitRegistration([FromBody] UserSubmitRegistrationInput input)
{
var userId = _authManager.UserId;
var user = await _userRepository.GetByIdAsync(userId);
if (user == null) throw Oops.Bah("用户不存在");
var eventInfo = await _eventRepository.GetByIdAsync(input.EventId);
if (eventInfo == null) throw Oops.Bah("赛事不存在");
var registration = await _registrationRepository.GetFirstAsync(x => x.EventId == input.EventId && x.CreatedUserId == userId && !x.DeleteMark);
if (registration == null)
{
registration = new EventRegistration
{
Id = _idGenerator.NewLong(),
EventId = input.EventId,
CreatedUserId = userId,
CreatedTime = DateTime.Now,
Status = AvailabilityStatus.Enable,
RegistrationStatus = input.IsDraft ? 0 : 1, // 0-待提交(草稿), 1-待审核
ContactName = user.Name,
ContactPhone = user.Mobile,
OrderNo = "",
RegistrationMode = 1
};
await _registrationRepository.InsertAsync(registration);
}
else
{
registration.RegistrationStatus = input.IsDraft ? 0 : 1;
registration.ContactName = user.Name;
registration.ContactPhone = user.Mobile;
registration.UpdatedTime = DateTime.Now;
registration.UpdatedUserId = userId;
await _registrationRepository.UpdateAsync(registration);
}
await _registrationItemRepository.DeleteAsync(x => x.RegistrationId == registration.Id);
await _registrationPersonRepository.DeleteAsync(x => x.RegistrationId == registration.Id);
var projects = await _projectRepository.GetListAsync(x => input.ProjectIds.Contains(x.Id));
var members = await _userRepository.GetListAsync(x => input.MemberIds.Contains(x.Id));
decimal totalAmount = 0;
int totalPersonCount = 0;
foreach (var project in projects)
{
var item = new EventRegistrationItem
{
Id = _idGenerator.NewLong(),
RegistrationId = registration.Id,
ProjectId = project.Id,
ProjectName = project.Name,
UnitPrice = project.Fee,
PersonCount = members.Count(),
TotalPrice = project.Fee * members.Count(),
CreatedUserId = userId,
CreatedTime = DateTime.Now
};
await _registrationItemRepository.InsertAsync(item);
totalAmount += item.TotalPrice;
totalPersonCount += item.PersonCount;
foreach (var member in members)
{
var person = new EventRegistrationPerson
{
Id = _idGenerator.NewLong(),
RegistrationId = registration.Id,
RegistrationItemId = item.Id,
ProjectId = project.Id,
Name = member.Name,
Gender = member.Gender,
IdCard = member.IdCardNumber,
Phone = member.Mobile,
Age = member.Age,
BirthDate = member.BirthDate ?? DateTime.MinValue,
PersonType = 1,
CreatedUserId = userId,
CreatedTime = DateTime.Now
};
await _registrationPersonRepository.InsertAsync(person);
}
}
registration.TotalAmount = totalAmount;
registration.TotalPersonCount = totalPersonCount;
await _registrationRepository.UpdateAsync(registration);
return registration.Id;
}
///
/// 3.3 预览支付接口
///
[DisplayName("预览支付")]
[HttpGet("preview/{registrationId}")]
public async Task PreviewOrder([FromRoute] long registrationId)
{
var registration = await _registrationRepository.GetByIdAsync(registrationId);
if (registration == null) throw Oops.Bah("报名信息不存在");
var eventInfo = await _eventRepository.GetByIdAsync(registration.EventId);
var items = await _registrationItemRepository.GetListAsync(x => x.RegistrationId == registrationId);
var persons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == registrationId);
var projectOutputs = items.Select(i => new UserEventProjectListOutput
{
Id = i.ProjectId,
Name = i.ProjectName,
Fee = i.UnitPrice,
RegisteredCount = i.PersonCount
}).ToList();
var memberOutputs = persons.DistinctBy(p => p.IdCard).Select(p => new UserTeamMemberOutput
{
Name = p.Name,
Mobile = p.Phone,
IdCard = MaskIdCard(p.IdCard),
Gender = p.Gender.ToString()
}).ToList();
return new UserOrderPreviewOutput
{
EventId = eventInfo.Id,
EventName = eventInfo.EventName,
Projects = projectOutputs,
Members = memberOutputs,
TotalAmount = registration.TotalAmount,
FeeDetails = new Dictionary { { "报名费", registration.TotalAmount } }
};
}
///
/// 创建支付订单
///
[DisplayName("创建支付订单")]
[HttpPost("order/create")]
[UnitOfWork]
public async Task CreateOrder([FromBody] UserCreateOrderInput input)
{
var registration = await _registrationRepository.GetByIdAsync(input.RegistrationId);
if (registration == null) throw Oops.Bah("报名信息不存在");
var orderNo = _idGenerator.NewLong().ToString();
registration.OrderNo = orderNo;
registration.PaymentMethod = input.PaymentMethod;
registration.PaymentStatus = 0; // 未支付
await _registrationRepository.UpdateAsync(registration);
return new UserPaymentResultOutput
{
OrderNo = orderNo,
PayUrl = "http://mock-pay-url.com/" + orderNo,
Status = 0
};
}
#region 参赛记录相关
///
/// 1. 列表查询
///
[DisplayName("参赛记录列表查询")]
[HttpGet("/event/pc/registration/list")]
[Authorize(Roles = "PC_USER")]
public async Task> GetRegistrationList([FromQuery] RegistrationListQueryInput input)
{
var userId = _authManager.UserId;
var query = _registrationRepository.AsQueryable()
.InnerJoin((r, e) => r.EventId == e.Id)
.Where((r, e) => r.CreatedUserId == userId && !r.DeleteMark)
.WhereIF(input.EventId.HasValue, (r, e) => r.EventId == input.EventId)
.WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), (r, e) => r.TeamName.Contains(input.Keyword) || e.EventName.Contains(input.Keyword))
.OrderByDescending((r, e) => r.CreatedTime)
.Select((r, e) => new RegistrationItemDTO
{
RegistrationId = r.Id,
EventName = e.EventName,
TeamName = r.TeamName,
CreateTime = r.CreatedTime,
UpdateTime = r.UpdatedTime,
PayStatus = r.PaymentStatus == 1 ? "已支付" : "待付款", // 简化映射
CertStatus = "未颁发", // 暂无证书逻辑
HasMaterial = SqlFunc.HasValue(r.MaterialUrl),
HasVoucher = SqlFunc.HasValue(r.PaymentProofUrl),
MemberCount = r.TotalPersonCount,
MaxMember = 10, // 假设
ExpireTime = e.MaterialUploadEndTime
});
// 这里需要先把数据查出来再做复杂的状态转换,因为 SqlSugar Select 中不能写太复杂的 switch
var pagedResult = await query.ToPagedListAsync(input);
// 二次处理状态描述
foreach (var item in pagedResult.Rows)
{
// 状态映射逻辑
// status ∈ (待付款, 待上传素材, 已取消, 已报名, 待审核)
// 假设:RegStatus=4 -> 已取消
// RegStatus=1 -> 待审核
// PayStatus=0 -> 待付款
// PayStatus=1 && !HasMaterial -> 待上传素材
// PayStatus=1 && HasMaterial -> 已报名
// 注意:这里需要再次查询原始状态值才能准确判断,上面的Select只映射了部分。
// 优化:Select时把原始状态值带出来
// 重新查询一遍原始数据比较准确
}
// 重新构建查询以支持复杂逻辑
var list = await _registrationRepository.AsQueryable()
.InnerJoin((r, e) => r.EventId == e.Id)
.Where((r, e) => r.CreatedUserId == userId && !r.DeleteMark)
.WhereIF(input.EventId.HasValue, (r, e) => r.EventId == input.EventId)
.WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), (r, e) => r.TeamName.Contains(input.Keyword) || e.EventName.Contains(input.Keyword))
.OrderByDescending((r, e) => r.CreatedTime)
.Select((r, e) => new
{
r.Id, r.EventId, e.EventName, r.TeamName, r.RegistrationStatus, r.PaymentStatus, r.MaterialUrl, r.PaymentProofUrl,
r.CreatedTime, r.UpdatedTime, r.TotalPersonCount, e.MaterialUploadEndTime
})
.ToPagedListAsync(input);
var resultItems = list.Rows.Select(x => {
string statusDesc = "未知";
if (x.RegistrationStatus == 4) statusDesc = "已取消";
else if (x.RegistrationStatus == 1) statusDesc = "待审核"; // 假设1是待审核
else if (x.PaymentStatus == 0) statusDesc = "待付款";
else if (x.PaymentStatus == 1 && string.IsNullOrEmpty(x.MaterialUrl)) statusDesc = "待上传素材";
else if (x.PaymentStatus == 1 && !string.IsNullOrEmpty(x.MaterialUrl)) statusDesc = "已报名"; // 或已完成
return new RegistrationItemDTO
{
RegistrationId = x.Id,
EventName = x.EventName,
TeamName = x.TeamName,
Status = statusDesc,
CreateTime = x.CreatedTime,
UpdateTime = x.UpdatedTime,
PayStatus = x.PaymentStatus == 1 ? "已支付" : (x.PaymentStatus == 4 ? "待审核" : "待付款"),
CertStatus = "未颁发",
HasMaterial = !string.IsNullOrEmpty(x.MaterialUrl),
HasVoucher = !string.IsNullOrEmpty(x.PaymentProofUrl),
MemberCount = x.TotalPersonCount,
MaxMember = 10,
ExpireTime = x.MaterialUploadEndTime
};
}).ToList();
return new PageResult
{
PageNo = list.PageNo,
PageSize = list.PageSize,
Total = list.Total,
Pages = list.Pages,
Rows = resultItems
};
}
///
/// 2. 取消报名
///
[DisplayName("取消报名")]
[HttpPut("/event/pc/registration/{registrationId}/cancel")]
[Authorize(Roles = "PC_USER")]
[UnitOfWork]
public async Task> CancelRegistration([FromRoute] long registrationId)
{
var userId = _authManager.UserId;
var registration = await _registrationRepository.GetFirstAsync(x => x.Id == registrationId && x.CreatedUserId == userId);
if (registration == null) throw Oops.Bah("报名记录不存在");
// 仅允许 status ∈ (待付款, 待上传素材) 可取消
// 待付款: PayStatus == 0
// 待上传素材: PayStatus == 1 && !HasMaterial
// 已取消: RegStatus == 4
if (registration.RegistrationStatus == 4) throw Oops.Bah("订单已取消");
bool canCancel = false;
if (registration.PaymentStatus == 0) canCancel = true;
else if (registration.PaymentStatus == 1 && string.IsNullOrEmpty(registration.MaterialUrl)) canCancel = true;
if (!canCancel)
{
// 已付款且已上传素材的报名需走退赛流程
throw Oops.Bah("当前状态无法直接取消,请联系管理员退赛");
}
registration.RegistrationStatus = 4; // 已取消
registration.UpdatedUserId = userId;
registration.UpdatedTime = DateTime.Now;
// registration.CancelReason = "用户主动取消"; // 需扩展字段
await _registrationRepository.UpdateAsync(registration);
// 记录日志 (暂不需要)
/*
await _eventLogRepository.InsertAsync(new EventLog
{
RegistrationId = registration.Id,
LogType = EventLogType.Cancel,
Description = "用户取消报名",
OperatorId = userId,
CreatedTime = DateTime.Now
});
*/
return new ResponseDTO { Code = 200, Success = true, Data = true, Message = "取消成功" };
}
///
/// 3. 上传素材
///
[DisplayName("上传素材")]
[HttpPost("/event/pc/registration/{registrationId}/material")]
[Authorize(Roles = "PC_USER")]
[UnitOfWork]
public async Task>> UploadMaterial([FromRoute] long registrationId, [FromForm] UploadMaterialInput input)
{
var userId = _authManager.UserId;
var registration = await _registrationRepository.GetFirstAsync(x => x.Id == registrationId && x.CreatedUserId == userId);
if (registration == null) throw Oops.Bah("报名记录不存在");
// 仅 status = 待上传素材 或 已上传素材 允许再次上传
// 即 PayStatus == 1 (已支付)
if (registration.PaymentStatus != 1) throw Oops.Bah("未支付或状态不正确,无法上传素材");
// 总素材大小累计不超过 100 MB (简化校验,假设单次)
long totalSize = input.Files.Sum(f => f.Length);
if (totalSize > 100 * 1024 * 1024) throw Oops.Bah("素材总大小超过100MB");
var result = new List();
foreach (var file in input.Files)
{
var uploadResult = await _fileService.Upload(file); // 假设 FileService 支持 IFormFile
var item = uploadResult.FirstOrDefault(); // 假设返回 List
if (item != null)
{
result.Add(new MaterialDTO
{
MaterialId = _idGenerator.NewLong().ToString(),
OssUrl = item.Url,
FileName = item.Name,
FileSize = file.Length,
UploadTime = DateTime.Now
});
}
}
if (result.Any())
{
registration.MaterialUrl = result.First().OssUrl; // 更新主表
await _registrationRepository.UpdateAsync(registration);
// 记录日志 (暂不需要)
/*
await _eventLogRepository.InsertAsync(new EventLog
{
RegistrationId = registration.Id,
LogType = EventLogType.UploadMaterial,
Description = $"用户上传素材: {result.Count}个文件",
OperatorId = userId,
CreatedTime = DateTime.Now
});
*/
}
return new ResponseDTO> { Code = 200, Success = true, Data = result, Message = "上传成功" };
}
///
/// 4. 上传付款凭证
///
[DisplayName("上传付款凭证")]
[HttpPost("/event/pc/registration/{registrationId}/voucher")]
[Authorize(Roles = "PC_USER")]
[UnitOfWork]
public async Task> UploadVoucher([FromRoute] long registrationId, [FromForm] UploadVoucherInput input)
{
var userId = _authManager.UserId;
var registration = await _registrationRepository.GetFirstAsync(x => x.Id == registrationId && x.CreatedUserId == userId);
if (registration == null) throw Oops.Bah("报名记录不存在");
// 仅 status = 待付款 允许上传
if (registration.PaymentStatus != 0) throw Oops.Bah("当前状态不允许上传凭证");
var uploadResult = await _fileService.Upload(input.File);
var fileInfo = uploadResult.First();
registration.PaymentProofUrl = fileInfo.Url;
registration.PaymentStatus = 4; // 假设 4 = 待审核
registration.UpdatedTime = DateTime.Now;
// registration.VoucherUploadTime = DateTime.Now;
await _registrationRepository.UpdateAsync(registration);
// 记录日志 (暂不需要)
/*
await _eventLogRepository.InsertAsync(new EventLog
{
RegistrationId = registration.Id,
LogType = EventLogType.UploadVoucher,
Description = "用户上传付款凭证",
OperatorId = userId,
CreatedTime = DateTime.Now
});
*/
return new ResponseDTO
{
Code = 200, Success = true, Message = "上传成功",
Data = new VoucherDTO
{
VoucherId = _idGenerator.NewLong().ToString(),
OssUrl = fileInfo.Url,
AuditStatus = "待审核"
}
};
}
///
/// 5.1 查看队员列表
///
[DisplayName("查看队员列表")]
[HttpGet("/event/pc/registration/{registrationId}/member")]
[Authorize(Roles = "PC_USER")]
public async Task> GetTeamMembers([FromRoute] long registrationId)
{
var userId = _authManager.UserId;
var registration = await _registrationRepository.GetFirstAsync(x => x.Id == registrationId && x.CreatedUserId == userId);
if (registration == null) throw Oops.Bah("报名记录不存在");
var persons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == registrationId);
// 需要关联 AppUser 获取更多信息 (Email 等)
// 这里简化处理
return persons.Select(p => new TeamMemberDTO
{
UserId = 0, // 需关联 AppUser
RealName = p.Name,
Phone = p.Phone,
Email = "", // 暂无
Role = p.PersonType == 3 ? "队长" : "队员", // 假设
InviteStatus = "已加入",
JoinTime = p.CreatedTime
}).ToList();
}
///
/// 5.2 更换队员
///
[DisplayName("更换队员")]
[HttpPut("/event/pc/registration/{registrationId}/member/{userId}/replace")]
[Authorize(Roles = "PC_USER")]
[UnitOfWork]
public async Task> ReplaceMember([FromRoute] long registrationId, [FromRoute] long userId, [FromBody] ReplaceMemberInput input)
{
var currentUserId = _authManager.UserId;
// 校验队长权限: 仅创建者(队长)可操作
var registration = await _registrationRepository.GetFirstAsync(x => x.Id == registrationId && x.CreatedUserId == currentUserId);
if (registration == null) throw Oops.Bah("无权操作或记录不存在");
var person = await _registrationPersonRepository.GetFirstAsync(x => x.RegistrationId == registrationId && x.Id == userId);
if (person == null) throw Oops.Bah("队员不存在");
// 被替换人 role ≠ 队长 (假设 PersonType=3 是队长)
if (person.PersonType == 3) throw Oops.Bah("不能替换队长");
// 检查新队员
var newUser = await _userRepository.GetByIdAsync(input.NewUserId);
if (newUser == null) throw Oops.Bah("新队员不存在");
// 检查新队员是否已报名该赛事 (简化检查: 是否在 Person 表中存在该 EventId 的记录)
// var exists = await _registrationPersonRepository.AnyAsync(x => x.EventId == registration.EventId && x.IdCard == newUser.IdCardNumber);
// if (exists) throw Oops.Bah("新队员已报名该赛事");
long oldUserId = person.Id; // 这里应该是 AppUserId,但 Person 表没存 AppUserId,只存了 Name/Phone
// 更新信息
person.Name = newUser.Name;
person.IdCard = newUser.IdCardNumber;
person.Phone = newUser.Mobile;
person.UpdatedTime = DateTime.Now;
await _registrationPersonRepository.UpdateAsync(person);
// 记录日志 (暂不需要)
/*
await _eventLogRepository.InsertAsync(new EventLog
{
RegistrationId = registration.Id,
LogType = EventLogType.ReplaceMember,
Description = $"更换队员: {oldUserId} -> {input.NewUserId}",
OperatorId = currentUserId,
CreatedTime = DateTime.Now
});
*/
return new ResponseDTO { Code = 200, Success = true, Data = true, Message = "更换成功" };
}
///
/// 6. 查看证书
///
[DisplayName("查看证书")]
[HttpGet("/event/pc/registration/{registrationId}/certificate")]
[Authorize(Roles = "PC_USER")]
public async Task> GetCertificate([FromRoute] long registrationId)
{
// 2-已发放
var cert = await _certificateRepository.GetFirstAsync(x => x.RegistrationId == registrationId && x.CertificateStatus == 2);
if (cert == null) return new ResponseDTO { Code = 404, Message = "证书未颁发", Success = false };
return new ResponseDTO
{
Code = 200, Success = true,
Data = new CertificateDTO
{
CertUrl = cert.CertificateUrl + "?sas_token=mock_10min_token", // 模拟带签名 URL
CertNumber = cert.CertificateNo,
IssueTime = cert.IssuedTime ?? DateTime.MinValue,
ExpireTime = null
}
};
}
#endregion
private string GetRegistrationStatusDesc(int status)
{
return status switch
{
0 => "待提交",
1 => "审核中",
2 => "已报名",
3 => "审核拒绝",
4 => "已取消",
_ => "未知"
};
}
}