YD_Event.Admin/YD_Event.Application/Client/EventClientController.cs

596 lines
24 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.Mvc;
using YD_Event.Application.Client.Dtos;
using YD_Event.Core.Entities;
using YD_Event.Core.Enum;
using SqlSugar;
using Furion.DatabaseAccessor;
using Microsoft.AspNetCore.Authorization;
using Furion.FriendlyException;
using System.Security.Claims;
namespace YD_Event.Application.Client;
/// <summary>
/// 小程序-赛事接口
/// </summary>
[ApiDescriptionSettings("微信小程序接口")]
public class EventClientController : IDynamicApiController
{
private readonly ISqlSugarRepository<EventInfo> _eventRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Project> _projectRepository;
private readonly ISqlSugarRepository<ProjectGroup> _groupRepository;
private readonly ISqlSugarRepository<EventRegistration> _registrationRepository;
private readonly ISqlSugarRepository<EventRegistrationItem> _registrationItemRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Banner> _bannerRepository;
private readonly ISqlSugarRepository<EventUnit> _unitRepository;
private readonly ISqlSugarRepository<EventRegistrationPerson> _personRepository;
private readonly ISqlSugarRepository<EventRegistrationLimit> _limitRepository;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IIdGenerator _idGenerator;
public EventClientController(
ISqlSugarRepository<EventInfo> eventRepository,
ISqlSugarRepository<YD_Event.Core.Entities.Project> projectRepository,
ISqlSugarRepository<ProjectGroup> groupRepository,
ISqlSugarRepository<EventRegistration> registrationRepository,
ISqlSugarRepository<EventRegistrationItem> registrationItemRepository,
ISqlSugarRepository<YD_Event.Core.Entities.Banner> bannerRepository,
ISqlSugarRepository<EventUnit> unitRepository,
ISqlSugarRepository<EventRegistrationPerson> personRepository,
ISqlSugarRepository<EventRegistrationLimit> limitRepository,
IHttpContextAccessor httpContextAccessor,
IIdGenerator idGenerator)
{
_eventRepository = eventRepository;
_projectRepository = projectRepository;
_groupRepository = groupRepository;
_registrationRepository = registrationRepository;
_registrationItemRepository = registrationItemRepository;
_bannerRepository = bannerRepository;
_unitRepository = unitRepository;
_personRepository = personRepository;
_limitRepository = limitRepository;
_httpContextAccessor = httpContextAccessor;
_idGenerator = idGenerator;
}
private long GetCurrentUserId()
{
var userIdStr = _httpContextAccessor.HttpContext?.User?.FindFirst(AuthClaimsConst.AuthIdKey)?.Value;
if (string.IsNullOrEmpty(userIdStr) || !long.TryParse(userIdStr, out long userId))
{
throw Oops.Bah("未登录或登录已失效");
}
return userId;
}
/// <summary>
/// 获取轮播图列表
/// </summary>
/// <returns></returns>
[HttpGet("banners")]
[AllowAnonymous]
public async Task<List<BannerDto>> GetBannerList()
{
var list = await _bannerRepository.AsQueryable()
.Where(b => !b.DeleteMark && b.Status == AvailabilityStatus.Enable)
.OrderByDescending(b => b.Sort)
.ToListAsync();
return list.Select(b => new BannerDto
{
Title = b.Title,
ImageUrl = b.ImageUrl,
LinkType = b.LinkType,
LinkUrl = b.LinkUrl,
EventId = b.EventId
}).ToList();
}
/// <summary>
/// 获取赛事列表
/// </summary>
/// <param name="page">页码</param>
/// <param name="pageSize">页大小</param>
/// <param name="status">状态筛选:0-全部 1-报名中 2-比赛中 3-已结束</param>
/// <param name="keyword">搜索关键字</param>
/// <returns></returns>
[HttpGet("list")]
[AllowAnonymous] // 允许未登录查看列表?根据需求,原型中有“我的”,可能需要登录。但通常列表可公开。这里先设为允许匿名,获取详情再校验。
// 修正:Class上有[Authorize],如果需要匿名需显式[AllowAnonymous]。
// 假设小程序进入即登录,保持Authorize
public async Task<PageResult<EventListDto>> GetEventList([FromQuery] int page = 1, [FromQuery] int pageSize = 10, [FromQuery] int status = 0, [FromQuery] string? keyword = null)
{
var query = _eventRepository.AsQueryable()
.Where(e => !e.DeleteMark && e.Status == AvailabilityStatus.Enable)
.WhereIF(!string.IsNullOrEmpty(keyword), e => e.EventName.Contains(keyword));
// 状态筛选逻辑
// 1-报名中: Now >= RegStart && Now <= RegEnd
// 2-比赛中: Now >= MatchStart && Now <= MatchEnd
// 3-已结束: Now > MatchEnd
var now = DateTime.Now;
if (status == 1)
{
query = query.Where(e => now >= e.RegistrationStartTime && now <= e.RegistrationEndTime);
}
else if (status == 2)
{
query = query.Where(e => now >= e.MatchStartTime && now <= e.MatchEndTime);
}
else if (status == 3)
{
query = query.Where(e => now > e.MatchEndTime);
}
// 排序:优先按报名开始时间倒序
query = query.OrderByDescending(e => e.RegistrationStartTime);
var list = await query.ToPagedListAsync(new Pagination { PageNo = page, PageSize = pageSize });
var result = new PageResult<EventListDto>
{
PageNo = list.PageNo,
PageSize = list.PageSize,
Total = list.Total,
Pages = list.Pages,
Rows = list.Rows.Select(e => new EventListDto
{
Id = e.Id,
EventName = e.EventName,
CoverImage = e.CoverImage,
EventStatus = CalculateEventStatus(e), // 动态计算状态
RegistrationStartTime = e.RegistrationStartTime,
RegistrationEndTime = e.RegistrationEndTime,
MatchStartTime = e.MatchStartTime,
MatchEndTime = e.MatchEndTime,
Address = $"{e.Province}{e.City}{e.District}{e.DetailAddress}"
}).ToList()
};
return result;
}
/// <summary>
/// 获取赛事详情
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet("{id}")]
[AllowAnonymous]
public async Task<EventDetailDto> GetEventDetail(long id)
{
var evt = await _eventRepository.GetFirstAsync(e => e.Id == id && !e.DeleteMark);
if (evt == null) throw Oops.Bah("赛事不存在");
// 获取项目及组别
var projects = await _projectRepository.AsQueryable()
.LeftJoin<ProjectType>((p, t) => p.ProjectTypeId == t.Id)
.Where(p => p.EventId == id && !p.DeleteMark && p.Status == AvailabilityStatus.Enable)
.Select((p, t) => new {
p.Id,
p.Name,
p.Fee,
TypeName = t.Name
})
.ToListAsync();
var projectIds = projects.Select(p => p.Id).ToList();
var groups = await _groupRepository.AsQueryable()
.Where(g => projectIds.Contains(g.ProjectId) && !g.DeleteMark)
.ToListAsync();
// 获取单位
var units = await _unitRepository.AsQueryable()
.Where(u => u.EventId == id && !u.DeleteMark)
.OrderBy(u => u.Sort)
.ToListAsync();
// 获取限制规则
var limit = await _limitRepository.GetFirstAsync(l => l.EventId == id && !l.DeleteMark);
var detail = new EventDetailDto
{
Id = evt.Id,
EventName = evt.EventName,
CoverImage = evt.CoverImage,
EventStatus = CalculateEventStatus(evt),
RegistrationStartTime = evt.RegistrationStartTime,
RegistrationEndTime = evt.RegistrationEndTime,
MatchStartTime = evt.MatchStartTime,
MatchEndTime = evt.MatchEndTime,
Address = $"{evt.Province}{evt.City}{evt.District}{evt.DetailAddress}",
EventVideo = evt.EventVideo,
EventType = evt.EventType,
ChargeMode = evt.ChargeMode,
RegistrationType = evt.RegistrationType,
EventIntroduction = evt.EventIntroduction,
MaterialRemark = evt.MaterialRemark,
MaterialUploadStartTime = evt.MaterialUploadStartTime,
MaterialUploadEndTime = evt.MaterialUploadEndTime,
// 模拟规则文本,实际应从 EventInfo 扩展字段或关联表读取
RefundRule = "开赛前7天扣除金额5%,3-7天扣除金额10%,3天内扣除金额20%",
PointsRule = "参与积分:5积分;名次积分:1-8名 15积分,9-20名 10积分",
IndividualProjectLimit = limit?.IndividualProjectLimit ?? 0,
TeamProjectLimit = limit?.TeamProjectLimit ?? 0,
Units = units.Select(u => new EventUnitDto
{
UnitType = u.UnitType,
UnitName = u.UnitName,
LogoUrl = u.LogoUrl
}).ToList(),
Projects = projects.Select(p => new EventProjectDto
{
Id = p.Id,
Name = p.Name,
TypeName = p.TypeName,
Fee = p.Fee,
Groups = groups.Where(g => g.ProjectId == p.Id).Select(g => new EventProjectGroupDto
{
Id = g.Id,
GroupName = g.GroupName,
GenderRequirement = g.Gender == Gender.Male ? "男" : (g.Gender == Gender.Female ? "女" : "不限"),
AgeRequirement = $"{g.BirthDateStart:yyyy-MM-dd} 至 {g.BirthDateEnd:yyyy-MM-dd}"
}).ToList()
}).ToList()
};
return detail;
}
/// <summary>
/// 提交赛事报名
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("apply")]
[UnitOfWork]
public async Task<long> SubmitRegistration([FromBody] SubmitRegistrationInput input)
{
var userId = GetCurrentUserId();
// 0. 校验报名模式
if (input.RegistrationMode == 2) // 假设2为团队
throw Oops.Bah("团队报名请前往网页端进行操作");
var evt = await _eventRepository.GetFirstAsync(e => e.Id == input.EventId);
if (evt == null) throw Oops.Bah("赛事不存在");
// 1. 校验时间
if (DateTime.Now < evt.RegistrationStartTime || DateTime.Now > evt.RegistrationEndTime)
throw Oops.Bah("不在报名时间内");
// 2. 校验重复报名 (同一赛事同一人只能报一次?还是可以多次?通常一次报名包含多个项目)
// 假设同一用户对同一赛事只能有一个“未取消”的报名记录
var exist = await _registrationRepository.IsAnyAsync(r => r.EventId == input.EventId && r.CreatedUserId == userId && r.RegistrationStatus != 4); // 4=已取消
if (exist) throw Oops.Bah("您已报名该赛事,请勿重复提交");
// 3. 校验项目有效性 & 限制
if (input.Items == null || !input.Items.Any()) throw Oops.Bah("请至少选择一个项目");
var limit = await _limitRepository.GetFirstAsync(l => l.EventId == input.EventId && !l.DeleteMark);
if (limit != null)
{
// 简单校验项目总数,更严谨需按项目类型统计
if (input.Items.Count > limit.IndividualProjectLimit)
throw Oops.Bah($"每人最多可报{limit.IndividualProjectLimit}个项目");
}
// 4. 创建报名主表
var registrationId = _idGenerator.NewLong();
var registration = new EventRegistration
{
Id = registrationId,
EventId = input.EventId,
RegistrationMode = input.RegistrationMode,
TeamName = input.RegistrationMode == 2 ? input.TeamName : null,
ContactName = input.ContactName,
ContactPhone = input.ContactPhone,
RegistrationStatus = 0, // 待提交/待支付
PaymentStatus = 0, // 未支付
RegistrationTime = DateTime.Now,
CreatedUserId = userId,
CreatedTime = DateTime.Now,
Status = AvailabilityStatus.Enable,
TotalPersonCount = 0 // 稍后计算
};
// 5. 创建报名明细 & 参赛人员 & 计算费用
decimal totalAmount = 0;
int totalPerson = 0;
var regItems = new List<EventRegistrationItem>();
var persons = new List<EventRegistrationPerson>();
foreach (var item in input.Items)
{
var project = await _projectRepository.GetFirstAsync(p => p.Id == item.ProjectId);
if (project == null) throw Oops.Bah($"项目不存在: {item.ProjectId}");
// 校验组别
// 略,假设前端传参正确,或需根据 GroupId 查
if (item.Persons == null || !item.Persons.Any())
throw Oops.Bah($"项目[{project.Name}]未填写参赛人信息");
var regItemId = _idGenerator.NewLong();
var regItem = new EventRegistrationItem
{
Id = regItemId,
RegistrationId = registrationId,
ProjectId = item.ProjectId,
ProjectName = project.Name,
GroupName = item.GroupName,
UnitPrice = project.Fee,
PersonCount = item.Persons.Count,
TotalPrice = project.Fee * item.Persons.Count,
CreatedUserId = userId,
CreatedTime = DateTime.Now
};
regItems.Add(regItem);
totalAmount += regItem.TotalPrice;
totalPerson += regItem.PersonCount;
// 添加参赛人员
foreach (var pInput in item.Persons)
{
persons.Add(new EventRegistrationPerson
{
Id = _idGenerator.NewLong(),
RegistrationId = registrationId,
RegistrationItemId = regItemId,
ProjectId = item.ProjectId,
PersonType = 1, // 运动员
Name = pInput.Name,
Gender = pInput.Gender,
IdCard = pInput.IdCard,
BirthDate = pInput.BirthDate,
Phone = pInput.Phone,
GroupName = item.GroupName,
ProjectName = project.Name,
CreatedUserId = userId,
CreatedTime = DateTime.Now
});
}
}
registration.TotalAmount = totalAmount;
registration.TotalPersonCount = totalPerson;
registration.OrderNo = DateTime.Now.ToString("yyyyMMddHHmmss") + new Random().Next(1000, 9999); // 简易订单号
await _registrationRepository.InsertAsync(registration);
await _registrationItemRepository.InsertRangeAsync(regItems);
await _personRepository.InsertRangeAsync(persons);
return registrationId;
}
/// <summary>
/// 获取支付信息
/// </summary>
[HttpGet("payment-info/{registrationId}")]
public async Task<PaymentInfoDto> GetPaymentInfo(long registrationId)
{
var userId = GetCurrentUserId();
var reg = await _registrationRepository.GetFirstAsync(r => r.Id == registrationId && r.CreatedUserId == userId);
if (reg == null) throw Oops.Bah("报名记录不存在");
return new PaymentInfoDto
{
RegistrationId = reg.Id,
Amount = reg.TotalAmount,
PaymentMethod = reg.PaymentMethod,
BankAccountName = "上海跃动文化科技有限公司",
BankAccountNo = "456498465198456261",
BankName = "招商银行", // 示例
WechatPayParams = new { appId = "wx8888888888888888", timeStamp = DateTimeOffset.Now.ToUnixTimeSeconds().ToString(), nonceStr = Guid.NewGuid().ToString("N"), package = "prepay_id=wx...", signType = "MD5", paySign = "..." } // 模拟
};
}
/// <summary>
/// 上传支付凭证(线下支付)
/// </summary>
[HttpPost("upload-voucher/{registrationId}")]
public async Task<bool> UploadPaymentVoucher(long registrationId, [FromBody] UploadVoucherInput input)
{
var userId = GetCurrentUserId();
var reg = await _registrationRepository.GetFirstAsync(r => r.Id == registrationId && r.CreatedUserId == userId);
if (reg == null) throw Oops.Bah("报名记录不存在");
reg.PaymentMethod = 1; // 线下
reg.PaymentProofUrl = input.VoucherUrl;
reg.RegistrationStatus = 1; // 待审核
reg.UpdatedTime = DateTime.Now;
reg.UpdatedUserId = userId;
return await _registrationRepository.UpdateAsync(reg);
}
/// <summary>
/// 更新参赛人信息
/// </summary>
[HttpPost("update-player")]
public async Task<bool> UpdateRegistrationPlayer([FromBody] UpdatePlayerInput input)
{
var userId = GetCurrentUserId();
var person = await _personRepository.GetFirstAsync(p => p.Id == input.PersonId);
if (person == null) throw Oops.Bah("参赛人不存在");
// 校验权限:确保是该用户创建的报名
var reg = await _registrationRepository.GetFirstAsync(r => r.Id == person.RegistrationId);
if (reg == null || reg.CreatedUserId != userId) throw Oops.Bah("无权操作");
person.Name = input.Name;
person.IdCard = input.IdCard;
person.Gender = input.Gender;
person.Phone = input.Phone;
person.UpdatedTime = DateTime.Now;
person.UpdatedUserId = userId;
return await _personRepository.UpdateAsync(person);
}
/// <summary>
/// 获取我的已报赛事列表
/// </summary>
/// <returns></returns>
[HttpGet("my-events")]
public async Task<PageResult<MyEventListDto>> GetMyEventList([FromQuery] int page = 1, [FromQuery] int pageSize = 10)
{
var userId = GetCurrentUserId();
var query = _registrationRepository.AsQueryable()
.LeftJoin<EventInfo>((r, e) => r.EventId == e.Id)
.Where(r => r.CreatedUserId == userId && !r.DeleteMark)
.OrderByDescending(r => r.RegistrationTime)
.Select((r, e) => new MyEventListDto
{
RegistrationId = r.Id,
EventId = r.EventId,
EventName = e.EventName,
CoverImage = e.CoverImage,
RegistrationStatus = r.RegistrationStatus,
PaymentStatus = r.PaymentStatus,
RegistrationTime = r.RegistrationTime,
ProjectCount = SqlFunc.Subqueryable<EventRegistrationItem>().Where(i => i.RegistrationId == r.Id).Count()
});
return await query.ToPagedListAsync(new Pagination { PageNo = page, PageSize = pageSize });
}
/// <summary>
/// 获取报名详情(包含参赛人)
/// </summary>
[HttpGet("my-registration/{registrationId}")]
public async Task<RegistrationDetailDto> GetRegistrationDetail(long registrationId)
{
var userId = GetCurrentUserId();
var reg = await _registrationRepository.AsQueryable()
.LeftJoin<EventInfo>((r, e) => r.EventId == e.Id)
.Where(r => r.Id == registrationId && r.CreatedUserId == userId)
.Select((r, e) => new {
r.Id,
r.EventId,
e.EventName,
e.CoverImage,
r.RegistrationStatus,
r.PaymentStatus,
r.TotalAmount,
r.OrderNo,
r.RegistrationTime,
r.ReviewRemark,
// 为了计算 EventStatus
RegStart = e.RegistrationStartTime,
RegEnd = e.RegistrationEndTime,
MatchStart = e.MatchStartTime,
MatchEnd = e.MatchEndTime
})
.FirstAsync();
if (reg == null) throw Oops.Bah("报名记录不存在");
// 计算 EventStatus
EventStatus status = EventStatus.Enrolling;
var now = DateTime.Now;
if (now >= reg.RegStart && now <= reg.RegEnd) status = EventStatus.Enrolling;
else if (now >= reg.MatchStart && now <= reg.MatchEnd) status = EventStatus.Ongoing;
else if (now > reg.MatchEnd) status = EventStatus.Ended;
// 获取 Items 和 Persons
var items = await _registrationItemRepository.AsQueryable()
.Where(i => i.RegistrationId == registrationId)
.ToListAsync();
var persons = await _personRepository.AsQueryable()
.Where(p => p.RegistrationId == registrationId)
.ToListAsync();
return new RegistrationDetailDto
{
RegistrationId = reg.Id,
EventId = reg.EventId,
EventName = reg.EventName,
CoverImage = reg.CoverImage,
EventStatus = status,
RegistrationStatus = reg.RegistrationStatus,
PaymentStatus = reg.PaymentStatus,
TotalAmount = reg.TotalAmount,
OrderNo = reg.OrderNo,
RegistrationTime = reg.RegistrationTime,
ReviewRemark = reg.ReviewRemark,
Items = items.Select(i => new RegistrationItemDto
{
Id = i.Id,
ProjectName = i.ProjectName,
GroupName = i.GroupName,
Price = i.UnitPrice, // 单价
Persons = persons.Where(p => p.RegistrationItemId == i.Id).Select(p => new RegistrationPersonDto
{
Id = p.Id,
Name = p.Name,
IdCard = p.IdCard,
Gender = p.Gender,
Phone = p.Phone
}).ToList()
}).ToList()
};
}
/// <summary>
/// 查询报名状态
/// </summary>
/// <param name="registrationId"></param>
/// <returns></returns>
[HttpGet("status/{registrationId}")]
public async Task<RegistrationStatusDto> GetRegistrationStatus(long registrationId)
{
var userId = GetCurrentUserId();
var reg = await _registrationRepository.GetFirstAsync(r => r.Id == registrationId && r.CreatedUserId == userId);
if (reg == null) throw Oops.Bah("报名记录不存在");
return new RegistrationStatusDto
{
RegistrationId = reg.Id,
RegistrationStatus = reg.RegistrationStatus,
PaymentStatus = reg.PaymentStatus,
RejectReason = reg.ReviewRemark
};
}
/// <summary>
/// 取消报名
/// </summary>
/// <param name="registrationId"></param>
/// <returns></returns>
[HttpPost("cancel/{registrationId}")]
public async Task<bool> CancelRegistration(long registrationId)
{
var userId = GetCurrentUserId();
var reg = await _registrationRepository.GetFirstAsync(r => r.Id == registrationId && r.CreatedUserId == userId);
if (reg == null) throw Oops.Bah("报名记录不存在");
// 校验状态:只有待提交、待审核、未支付状态可直接取消
// 已支付状态需走退款流程(暂不实现)
if (reg.PaymentStatus == 1) throw Oops.Bah("已支付订单请联系客服退款");
if (reg.RegistrationStatus == 4) throw Oops.Bah("订单已取消,无需重复操作");
reg.RegistrationStatus = 4; // 4=已取消
reg.UpdatedTime = DateTime.Now;
reg.UpdatedUserId = userId;
return await _registrationRepository.UpdateAsync(reg);
}
private EventStatus CalculateEventStatus(EventInfo e)
{
var now = DateTime.Now;
if (now >= e.RegistrationStartTime && now <= e.RegistrationEndTime) return EventStatus.Enrolling; // 报名中
if (now >= e.MatchStartTime && now <= e.MatchEndTime) return EventStatus.Ongoing; // 比赛中
if (now > e.MatchEndTime) return EventStatus.Ended; // 已结束
return EventStatus.Enrolling; // 默认? 或者新增未开始状态
}
}