using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using YD_Event.Application.Event.Dtos;
using YD_Event.Application.Common;
using YD_Event.Core.Entities;
using YD_Event.Core.Enum;
using Easy.Core;
using SqlSugar;
using Mapster;
using YD_Event.Application.Auth;
using Furion.DynamicApiController;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Authorization;
using System.ComponentModel;
using Furion.FriendlyException;
using Furion.DatabaseAccessor;
using Furion.DependencyInjection;
using YD_Event.Core.Infrastructure.WeChat;
using YD_Event.Core.Domain;
using Microsoft.Extensions.Logging;
namespace YD_Event.Application.Event;
///
/// 用户端参赛记录管理
///
public class UserEventRecordService : IDynamicApiController, ITransient
{
private readonly ISqlSugarRepository _registrationRepository;
private readonly ISqlSugarRepository _registrationPersonRepository;
private readonly ISqlSugarRepository _registrationItemRepository;
private readonly ISqlSugarRepository _projectRepository;
private readonly ISqlSugarRepository _userRepository;
private readonly ISqlSugarRepository _certificateRepository;
private readonly ISqlSugarRepository _orderRepository;
private readonly ISqlSugarRepository _eventRepository;
private readonly ISqlSugarClient _db;
private readonly AuthManager _authManager;
private readonly IIdGenerator _idGenerator;
private readonly FileService _fileService;
private readonly ILogger _logger;
private readonly UnifiedRegistrationService _unifiedRegistrationService;
public UserEventRecordService(
ISqlSugarRepository registrationRepository,
ISqlSugarRepository registrationPersonRepository,
ISqlSugarRepository registrationItemRepository,
ISqlSugarRepository projectRepository,
ISqlSugarRepository userRepository,
ISqlSugarRepository certificateRepository,
ISqlSugarRepository orderRepository,
ISqlSugarRepository eventRepository,
ISqlSugarClient db,
AuthManager authManager,
IIdGenerator idGenerator,
FileService fileService,
ILogger logger,
UnifiedRegistrationService unifiedRegistrationService)
{
_registrationRepository = registrationRepository;
_registrationPersonRepository = registrationPersonRepository;
_registrationItemRepository = registrationItemRepository;
_projectRepository = projectRepository;
_userRepository = userRepository;
_certificateRepository = certificateRepository;
_orderRepository = orderRepository;
_eventRepository = eventRepository;
_db = db;
_authManager = authManager;
_idGenerator = idGenerator;
_fileService = fileService;
_logger = logger;
_unifiedRegistrationService = unifiedRegistrationService;
}
///
/// 1. 列表查询
///
[DisplayName("参赛记录列表查询")]
[HttpGet("list")]
public async Task> GetRegistrationList([FromQuery] RegistrationListQueryInput input)
{
var userId = _authManager.UserId;
var list = await _registrationRepository.AsQueryable()
.InnerJoin((r, e) => r.EventId == e.Id)
.LeftJoin((r, e, fc) => e.Id == fc.EventId)
.Where((r, e, fc) => r.CreatedUserId == userId && !r.DeleteMark)
// 支付状态筛选:
// - 线下支付时:显示未支付(0)和已支付(1)状态的数据
// - 线上支付时:仅显示已支付(1)状态的数据
.Where((r, e, fc) =>
(r.PaymentMethod == 1 && (r.PaymentStatus == 0 || r.PaymentStatus == 1)) ||
(r.PaymentMethod == 2 && r.PaymentStatus == 1))
.WhereIF(input.EventId.HasValue, (r, e, fc) => r.EventId == input.EventId)
.WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), (r, e, fc) => r.TeamName.Contains(input.Keyword) || e.EventName.Contains(input.Keyword))
.OrderByDescending((r, e, fc) => r.CreatedTime)
.Select((r, e, fc) => new
{
r.Id, r.EventId, e.EventName, r.TeamName, r.PaymentStatus, r.MaterialUrl, r.PaymentProofUrl,
r.CreatedTime, r.UpdatedTime, r.TotalPersonCount, e.MaterialUploadEndTime,
e.CoverImage, e.EventType, e.EventStatus, e.RegistrationStartTime, e.RegistrationEndTime,
e.MatchStartTime, e.MatchEndTime,
fc.ChargeMode,
r.PaymentMethod
})
.ToPagedListAsync(input);
var resultItems = list.Rows.Select(x => {
return new RegistrationItemDTO
{
RegistrationId = x.Id,
EventName = x.EventName,
CoverImage = x.CoverImage,
EventType = (int?)x.EventType,
ChargeMode = (int?)x.ChargeMode,
EventStatus = (int?)x.EventStatus,
RegistrationStartTime = x.RegistrationStartTime,
RegistrationEndTime = x.RegistrationEndTime,
MaterialDeadline = x.MaterialUploadEndTime,
MatchStartTime = x.MatchStartTime,
MatchEndTime = x.MatchEndTime,
TeamName = x.TeamName,
CreateTime = x.CreatedTime,
UpdateTime = x.UpdatedTime,
PayStatus = x.PaymentStatus,
CertStatus = 0, // 未颁发
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("取消报名")]
[HttpPost("cancel/{registrationId}")]
[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("报名记录不存在");
if (registration.PaymentStatus == 4) throw Oops.Bah("订单已取消");
// 校验状态:只有未支付状态可直接取消,已支付需走退款流程
if (registration.PaymentStatus == 1) throw Oops.Bah("已支付订单请联系客服退款");
_logger.LogInformation("开始取消报名:报名ID={RegistrationId}, 操作人={UserId}, 当前状态={PaymentStatus}",
registrationId, userId, registration.PaymentStatus);
// 更新报名状态
registration.PaymentStatus = 4; // 已取消
registration.UpdatedUserId = userId;
registration.UpdatedTime = DateTime.Now;
await _registrationRepository.UpdateAsync(registration);
// 同步更新订单状态
if (!string.IsNullOrEmpty(registration.OrderNo))
{
var order = await _orderRepository.GetFirstAsync(o => o.OrderNo == registration.OrderNo);
if (order != null)
{
order.OrderStatus = OrderStatus.Cancelled;
order.UpdatedTime = DateTime.Now;
order.UpdatedUserId = userId;
await _orderRepository.UpdateAsync(order);
_logger.LogInformation("订单状态已同步更新:订单号={OrderNo}, 状态={OrderStatus}",
registration.OrderNo, OrderStatus.Cancelled);
}
else
{
_logger.LogWarning("未找到对应的订单记录:报名ID={RegistrationId}, 订单号={OrderNo}",
registrationId, registration.OrderNo);
}
}
_logger.LogInformation("取消报名成功:报名ID={RegistrationId}", registrationId);
return new ResponseDTO { Code = 200, Success = true, Data = true, Message = "取消成功" };
}
///
/// 3. 上传素材
///
[DisplayName("上传素材")]
[HttpPost("material/{registrationId}")]
[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);
var item = uploadResult.FirstOrDefault();
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);
}
return new ResponseDTO> { Code = 200, Success = true, Data = result, Message = "上传成功" };
}
///
/// 4. 上传付款凭证
///
[DisplayName("上传付款凭证")]
[HttpPost("voucher/{registrationId}")]
[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 = 0;
registration.UpdatedTime = DateTime.Now;
await _registrationRepository.UpdateAsync(registration);
return new ResponseDTO
{
Code = 200, Success = true, Message = "上传成功",
Data = new VoucherDTO
{
VoucherId = _idGenerator.NewLong().ToString(),
OssUrl = fileInfo.Url,
AuditStatus = "待审核"
}
};
}
///
/// 5.1 查看队员列表
///
[DisplayName("查看队员列表")]
[HttpGet("members/{registrationId}")]
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 == PersonType.Leader ? "队长" : "队员", // 假设
InviteStatus = "已加入",
JoinTime = p.CreatedTime
}).ToList();
}
///
/// 5.2 更换队员
///
[DisplayName("更换队员")]
[HttpPost("members/replace/{registrationId}/{userId}")]
[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 ≠ 队长
if (person.PersonType == PersonType.Leader) throw Oops.Bah("不能替换队长");
// 检查新队员
var newUser = await _userRepository.GetByIdAsync(input.NewUserId);
if (newUser == null) throw Oops.Bah("新队员不存在");
long oldUserId = person.Id;
// 更新信息
person.Name = newUser.Name;
person.IdCard = newUser.IdCardNumber;
person.Phone = newUser.Mobile;
person.UpdatedTime = DateTime.Now;
await _registrationPersonRepository.UpdateAsync(person);
return new ResponseDTO { Code = 200, Success = true, Data = true, Message = "更换成功" };
}
///
/// 6. 查看证书
///
[DisplayName("查看证书")]
[HttpGet("certificate/{registrationId}")]
public async Task> GetCertificate([FromRoute] long registrationId)
{
var userId = _authManager.UserId;
// 2-已发放
var cert = await _certificateRepository.GetFirstAsync(x => x.RegistrationId == registrationId && x.CertificateStatus == 2);
if (cert == null) return new ResponseDTO { Code = 404, Message = "证书未颁发", Success = false };
// 权限验证
bool hasPermission = await CheckCertificatePermissionAsync(userId, cert);
if (!hasPermission)
{
return new ResponseDTO { Code = 403, Message = "无权查看该证书", Success = false };
}
return new ResponseDTO
{
Code = 200, Success = true,
Data = new CertificateDTO
{
CertUrl = cert.CertificateUrl,
CertNumber = cert.CertificateNo,
IssueTime = cert.IssuedTime ?? DateTime.MinValue,
ExpireTime = null
}
};
}
///
/// 检查证书查看权限
///
private async Task CheckCertificatePermissionAsync(long currentUserId, EventCertificate cert)
{
// 如果是自己的证书,直接允许
if (cert.UserId == currentUserId)
{
return true;
}
// 检查当前用户是否是单位用户,且该证书属于其管理的团队成员
var currentUser = await _userRepository.GetByIdAsync(currentUserId);
if (currentUser == null)
{
return false;
}
// 如果当前用户有单位认证
if (currentUser.MerchantCertificationId.HasValue)
{
// 查询当前用户管理的团队成员
var teamMemberUserIds = await _db.Queryable()
.InnerJoin((t, tm) => t.Id == tm.TeamId)
.Where((t, tm) => t.CreatedUserId == currentUserId && !t.DeleteMark && !tm.DeleteMark)
.Select((t, tm) => tm.UserId)
.ToListAsync();
// 检查证书用户是否在团队成员列表中
if (teamMemberUserIds.Contains(cert.UserId))
{
return true;
}
}
return false;
}
///
/// 7. 取消报名预览
///
[DisplayName("取消报名预览")]
[HttpGet("cancel/preview/{registrationId}")]
public async Task CancelPreview([FromRoute] long registrationId)
{
var userId = _authManager.UserId;
_logger.LogInformation("调用统一取消报名预览:报名ID={RegistrationId}, 操作人={UserId}", registrationId, userId);
return await _unifiedRegistrationService.GetCancelPreviewAsync(registrationId, userId);
}
///
/// 8. 执行取消报名
///
[DisplayName("执行取消报名")]
[HttpPost("cancel/execute")]
[UnitOfWork]
public async Task CancelExecute([FromBody] CancelExecuteInput input)
{
var userId = _authManager.UserId;
_logger.LogInformation("调用统一执行取消报名:报名ID={RegistrationId}, 操作人={UserId}", input.RegistrationId, userId);
return await _unifiedRegistrationService.ExecuteCancelRegistrationAsync(input, userId);
}
///
/// 9. 获取参赛记录详情(包含项目和队员)
///
[DisplayName("获取参赛记录详情(包含项目和队员)")]
[HttpGet("detail-with-items/{registrationId}")]
public async Task GetRegistrationDetailWithItems([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 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 groupIds = items.Where(x => x.EventProjectGroupId.HasValue).Select(x => x.EventProjectGroupId.Value).Distinct().ToList();
var groups = new Dictionary();
if (groupIds.Any())
{
var groupList = await _db.Queryable()
.Where(x => groupIds.Contains(x.Id))
.ToListAsync();
groups = groupList.ToDictionary(x => x.Id);
}
// 获取项目信息(从Project表查询项目名称和完成人数)
var projectIds = items.Select(x => x.ProjectId).Distinct().ToList();
var projects = new Dictionary();
if (projectIds.Any())
{
var projectList = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id));
projects = projectList.ToDictionary(x => x.Id);
}
var result = new RegistrationDetailWithItemsDto
{
RegistrationId = registration.Id,
EventName = eventInfo?.EventName ?? "",
TeamName = registration.TeamName ?? ""
};
foreach (var item in items)
{
var itemMembers = persons.Where(p => p.RegistrationItemId == item.Id).ToList();
// 获取组别信息
EventProjectGroup? group = null;
if (item.EventProjectGroupId.HasValue && groups.TryGetValue(item.EventProjectGroupId.Value, out var foundGroup))
{
group = foundGroup;
}
// 获取项目信息
YD_Event.Core.Entities.Project? project = null;
if (projects.TryGetValue(item.ProjectId, out var foundProject))
{
project = foundProject;
}
result.Items.Add(new RegistrationItemWithMembersDto
{
RegistrationItemId = item.Id,
ProjectId = item.ProjectId,
// 优先从Project表获取项目名称,如果没有则使用报名时保存的名称
ProjectName = project?.Name ?? item.ProjectName ?? "",
GroupName = item.GroupName ?? "",
ProjectTypeName = item.ProjectTypeName ?? "",
PersonCount = item.PersonCount,
// 从Project表获取完成人数
ProjectCompletionCount = project?.CompletionCount ?? 0,
BirthDateStart = group?.BirthDateStart,
BirthDateEnd = group?.BirthDateEnd,
GenderRequirement = group?.Gender,
Members = itemMembers.Select(p => new RegistrationPersonDto
{
Id = p.Id,
Name = p.Name,
IdCardNumber = p.IdCard,
Gender = p.Gender,
Mobile = p.Phone,
BirthDate = p.BirthDate
}).ToList()
});
}
return result;
}
///
/// 10. 更新参赛队员
///
[DisplayName("更新参赛队员")]
[HttpPost("update-members")]
[UnitOfWork]
public async Task> UpdateRegistrationMembers([FromBody] UpdateRegistrationMembersInput input)
{
var userId = _authManager.UserId;
var registration = await _registrationRepository.GetFirstAsync(x => x.Id == input.RegistrationId && x.CreatedUserId == userId);
if (registration == null) throw Oops.Bah("报名记录不存在或无权操作");
_logger.LogInformation("开始更新参赛队员:报名ID={RegistrationId}, 操作人={UserId}", input.RegistrationId, userId);
// 获取所有需要查询的用户ID
var userIds = input.Items.SelectMany(x => x.Members).Where(x => x.UserId.HasValue).Select(x => x.UserId.Value).Distinct().ToList();
var users = new Dictionary();
if (userIds.Any())
{
var userList = await _userRepository.GetListAsync(x => userIds.Contains(x.Id));
users = userList.ToDictionary(x => x.Id);
}
foreach (var item in input.Items)
{
// 验证项目是否属于该报名
var regItem = await _registrationItemRepository.GetFirstAsync(x => x.Id == item.RegistrationItemId && x.RegistrationId == input.RegistrationId);
if (regItem == null) throw Oops.Bah($"项目记录不存在:{item.RegistrationItemId}");
// 删除旧的队员
await _registrationPersonRepository.DeleteAsync(x => x.RegistrationItemId == item.RegistrationItemId);
// 添加新的队员
var newPersons = new List();
foreach (var member in item.Members)
{
// 如果有UserId,从用户表获取完整信息
YD_Event.Core.Entities.AppUser? user = null;
if (member.UserId.HasValue && users.TryGetValue(member.UserId.Value, out var foundUser))
{
user = foundUser;
}
var name = member.Name ?? user?.Name;
var gender = member.Gender ?? user?.Gender ?? Gender.Male;
var idCardNumber = member.IdCardNumber ?? user?.IdCardNumber;
var birthDate = member.BirthDate ?? user?.BirthDate ?? DateTime.Now.AddYears(-20);
var mobile = member.Mobile ?? user?.Mobile;
// 验证必填字段
if (string.IsNullOrWhiteSpace(name)) throw Oops.Bah("队员姓名不能为空");
if (string.IsNullOrWhiteSpace(idCardNumber)) throw Oops.Bah("队员身份证号不能为空");
if (string.IsNullOrWhiteSpace(mobile)) throw Oops.Bah("队员手机号不能为空");
newPersons.Add(new EventRegistrationPerson
{
Id = _idGenerator.NewLong(),
RegistrationId = input.RegistrationId,
RegistrationItemId = item.RegistrationItemId,
ProjectId = regItem.ProjectId,
EventProjectGroupId = regItem.EventProjectGroupId,
GroupName = regItem.GroupName,
ProjectName = regItem.ProjectName,
Name = name,
Gender = gender,
IdCard = idCardNumber,
BirthDate = birthDate,
Phone = mobile,
Age = CalculateAge(birthDate),
PersonType = PersonType.Athlete,
UserId = member.UserId ?? 0,
CreatedUserId = userId,
CreatedTime = DateTime.Now
});
}
await _registrationPersonRepository.InsertRangeAsync(newPersons);
// 更新项目的人数
regItem.PersonCount = item.Members.Count;
regItem.UpdatedTime = DateTime.Now;
regItem.UpdatedUserId = userId;
await _registrationItemRepository.UpdateAsync(regItem);
}
// 更新报名总人数
var allPersonsCount = await _registrationPersonRepository.CountAsync(x => x.RegistrationId == input.RegistrationId);
registration.TotalPersonCount = allPersonsCount;
registration.UpdatedTime = DateTime.Now;
registration.UpdatedUserId = userId;
await _registrationRepository.UpdateAsync(registration);
_logger.LogInformation("更新参赛队员成功:报名ID={RegistrationId}", input.RegistrationId);
return new ResponseDTO { Code = 200, Success = true, Data = true, Message = "更新成功" };
}
///
/// 计算年龄
///
private int CalculateAge(DateTime birthDate)
{
var today = DateTime.Today;
var age = today.Year - birthDate.Year;
if (birthDate.Date > today.AddYears(-age)) age--;
return age;
}
}