2026-03-14 06:24:00 +08:00
|
|
|
|
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 System.Security.Claims;
|
|
|
|
|
|
using Furion.FriendlyException;
|
|
|
|
|
|
|
|
|
|
|
|
namespace YD_Event.Application.Client;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 小程序-个人中心接口
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[ApiDescriptionSettings("微信小程序接口")]
|
|
|
|
|
|
public class MyController : IDynamicApiController
|
|
|
|
|
|
{
|
2026-03-14 07:11:25 +08:00
|
|
|
|
private readonly ISqlSugarRepository<YD_Event.Core.Entities.AppUser> _appUserRepository;
|
|
|
|
|
|
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Team> _teamRepository;
|
2026-03-14 06:24:00 +08:00
|
|
|
|
private readonly ISqlSugarRepository<EventCertificate> _certificateRepository;
|
|
|
|
|
|
private readonly ISqlSugarRepository<PointsRecord> _pointsRecordRepository;
|
2026-03-14 07:11:25 +08:00
|
|
|
|
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Order> _orderRepository;
|
2026-03-14 06:24:00 +08:00
|
|
|
|
private readonly ISqlSugarRepository<EventRegistration> _registrationRepository;
|
|
|
|
|
|
private readonly ISqlSugarRepository<EventInfo> _eventRepository;
|
|
|
|
|
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
|
|
|
|
|
|
|
|
|
|
|
public MyController(
|
2026-03-14 07:11:25 +08:00
|
|
|
|
ISqlSugarRepository<YD_Event.Core.Entities.AppUser> appUserRepository,
|
|
|
|
|
|
ISqlSugarRepository<YD_Event.Core.Entities.Team> teamRepository,
|
2026-03-14 06:24:00 +08:00
|
|
|
|
ISqlSugarRepository<EventCertificate> certificateRepository,
|
|
|
|
|
|
ISqlSugarRepository<PointsRecord> pointsRecordRepository,
|
2026-03-14 07:11:25 +08:00
|
|
|
|
ISqlSugarRepository<YD_Event.Core.Entities.Order> orderRepository,
|
2026-03-14 06:24:00 +08:00
|
|
|
|
ISqlSugarRepository<EventRegistration> registrationRepository,
|
|
|
|
|
|
ISqlSugarRepository<EventInfo> eventRepository,
|
|
|
|
|
|
IHttpContextAccessor httpContextAccessor)
|
|
|
|
|
|
{
|
|
|
|
|
|
_appUserRepository = appUserRepository;
|
|
|
|
|
|
_teamRepository = teamRepository;
|
|
|
|
|
|
_certificateRepository = certificateRepository;
|
|
|
|
|
|
_pointsRecordRepository = pointsRecordRepository;
|
|
|
|
|
|
_orderRepository = orderRepository;
|
|
|
|
|
|
_registrationRepository = registrationRepository;
|
|
|
|
|
|
_eventRepository = eventRepository;
|
|
|
|
|
|
_httpContextAccessor = httpContextAccessor;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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("profile")]
|
|
|
|
|
|
public async Task<MyProfileDto> GetProfile()
|
|
|
|
|
|
{
|
|
|
|
|
|
var userId = GetCurrentUserId();
|
|
|
|
|
|
// 注意:AuthAccount.Id 和 AppUser.UserId 关联。这里假设登录的是 AuthAccount,需要查 AppUser
|
|
|
|
|
|
// 实际上 AppUser 表的 UserId 字段关联的是 AuthAccount 的 Id。
|
|
|
|
|
|
// 但 AppUser 本身也有 Id。
|
|
|
|
|
|
// 业务逻辑通常是:一个 AuthAccount 对应一个 AppUser。
|
|
|
|
|
|
|
|
|
|
|
|
var user = await _appUserRepository.GetFirstAsync(u => u.UserId == userId);
|
|
|
|
|
|
if (user == null) throw Oops.Bah("用户信息不存在");
|
|
|
|
|
|
|
|
|
|
|
|
// 统计证书数量
|
|
|
|
|
|
var certCount = await _certificateRepository.CountAsync(c => c.CreatedUserId == userId && c.CertificateStatus == 2);
|
|
|
|
|
|
|
|
|
|
|
|
// 积分 (需计算积分总和,PointsRecord 表有 Points 字段,Type 1-加 2-减)
|
|
|
|
|
|
// 简单计算:Sum(Type=1) - Sum(Type=2)
|
|
|
|
|
|
var pointsIn = await _pointsRecordRepository.AsQueryable().Where(p => p.UserId == userId && p.Type == PointsOperationType.Acquire).SumAsync(p => p.Points);
|
|
|
|
|
|
var pointsOut = await _pointsRecordRepository.AsQueryable().Where(p => p.UserId == userId && p.Type == PointsOperationType.Consume).SumAsync(p => p.Points);
|
|
|
|
|
|
var pointsBalance = (int)(pointsIn - pointsOut);
|
|
|
|
|
|
|
|
|
|
|
|
// 认证状态: AuditStatus: 0-待提交, 1-待审核, 2-通过, 3-拒绝.
|
|
|
|
|
|
int authStatus = 0;
|
|
|
|
|
|
if (string.IsNullOrEmpty(user.Name) || string.IsNullOrEmpty(user.IdCardNumber))
|
|
|
|
|
|
{
|
|
|
|
|
|
authStatus = 0; // 未认证
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (user.AuditStatus == AuditStatus.Pass)
|
|
|
|
|
|
{
|
|
|
|
|
|
authStatus = 1; // 已认证
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (user.AuditStatus == AuditStatus.PendingAudit)
|
|
|
|
|
|
{
|
|
|
|
|
|
authStatus = 2; // 审核中
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
authStatus = 3; // 失败/其他
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return new MyProfileDto
|
|
|
|
|
|
{
|
|
|
|
|
|
NickName = user.NickName ?? "微信用户",
|
|
|
|
|
|
Avatar = user.Avatar ?? "",
|
|
|
|
|
|
Mobile = MaskMobile(user.Mobile),
|
|
|
|
|
|
Points = pointsBalance,
|
|
|
|
|
|
CertificateCount = certCount,
|
|
|
|
|
|
AuthStatus = authStatus
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取实名认证信息
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
[HttpGet("auth")]
|
|
|
|
|
|
public async Task<MyAuthInfoDto> GetAuthInfo()
|
|
|
|
|
|
{
|
|
|
|
|
|
var userId = GetCurrentUserId();
|
|
|
|
|
|
var user = await _appUserRepository.GetFirstAsync(u => u.UserId == userId);
|
|
|
|
|
|
if (user == null) throw Oops.Bah("用户信息不存在");
|
|
|
|
|
|
|
|
|
|
|
|
return new MyAuthInfoDto
|
|
|
|
|
|
{
|
|
|
|
|
|
Name = user.Name,
|
|
|
|
|
|
Mobile = user.Mobile,
|
|
|
|
|
|
IdCardNumber = user.IdCardNumber, // 可以在前端脱敏
|
|
|
|
|
|
IdCardImageUrl = user.IdCardImageUrl,
|
|
|
|
|
|
AuditStatus = user.AuditStatus,
|
|
|
|
|
|
AuditRemark = user.AuditRemark
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 提交实名认证
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="input"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
[HttpPost("auth")]
|
|
|
|
|
|
public async Task<bool> SubmitAuth([FromBody] SubmitAuthInput input)
|
|
|
|
|
|
{
|
|
|
|
|
|
var userId = GetCurrentUserId();
|
|
|
|
|
|
var user = await _appUserRepository.GetFirstAsync(u => u.UserId == userId);
|
|
|
|
|
|
if (user == null) throw Oops.Bah("用户信息不存在");
|
|
|
|
|
|
|
|
|
|
|
|
if (user.AuditStatus == AuditStatus.Pass) throw Oops.Bah("已通过实名认证,无需重复提交");
|
|
|
|
|
|
if (user.AuditStatus == AuditStatus.PendingAudit) throw Oops.Bah("审核中,请勿重复提交");
|
|
|
|
|
|
|
|
|
|
|
|
user.Name = input.Name;
|
|
|
|
|
|
user.Mobile = input.Mobile;
|
|
|
|
|
|
user.IdCardNumber = input.IdCardNumber;
|
|
|
|
|
|
user.IdCardImageUrl = input.IdCardImageUrl;
|
|
|
|
|
|
user.AuditStatus = AuditStatus.PendingAudit; // 提交后变更为待审核
|
|
|
|
|
|
user.UpdatedTime = DateTime.Now;
|
|
|
|
|
|
|
|
|
|
|
|
return await _appUserRepository.UpdateAsync(user);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 我的团队列表
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
[HttpGet("teams")]
|
|
|
|
|
|
public async Task<PageResult<MyTeamDto>> GetTeams([FromQuery] MyPageInput input)
|
|
|
|
|
|
{
|
|
|
|
|
|
var userId = GetCurrentUserId();
|
|
|
|
|
|
// 逻辑:查找我是创建人(队长) 或 队员的团队
|
|
|
|
|
|
// 目前 Team 表只有 CreatedUserId,假设 CreatedUserId 就是队长。
|
|
|
|
|
|
// 如果有 TeamMember 表关联队员,需要联表。目前假设只查我创建的团队(根据原型图"我的团队"通常指我管理的)。
|
|
|
|
|
|
// 如果原型包含"我加入的",需要查 TeamMember。
|
|
|
|
|
|
// 鉴于 TeamMember 实体未直接给出,这里先查 CreatedUserId = userId
|
|
|
|
|
|
|
|
|
|
|
|
var query = _teamRepository.AsQueryable()
|
|
|
|
|
|
.Where(t => t.CreatedUserId == userId && !t.DeleteMark)
|
|
|
|
|
|
.Select(t => new MyTeamDto
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = t.Id,
|
|
|
|
|
|
Name = t.Name,
|
|
|
|
|
|
IsLeader = true,
|
|
|
|
|
|
MemberCount = 0 // 暂无 Member 表关联,置为 0 或需扩展
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return await query.ToPagedListAsync(input);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 我的证书列表
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
[HttpGet("certificates")]
|
|
|
|
|
|
public async Task<PageResult<MyCertificateDto>> GetCertificates([FromQuery] MyPageInput input)
|
|
|
|
|
|
{
|
|
|
|
|
|
var userId = GetCurrentUserId();
|
|
|
|
|
|
|
|
|
|
|
|
// 联表查询 EventInfo 获取赛事名称
|
|
|
|
|
|
var query = _certificateRepository.AsQueryable()
|
|
|
|
|
|
.LeftJoin<EventInfo>((c, e) => c.EventId == e.Id)
|
|
|
|
|
|
.Where(c => c.CreatedUserId == userId && c.CertificateStatus == 2 && !c.DeleteMark)
|
|
|
|
|
|
.Select((c, e) => new MyCertificateDto
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = c.Id,
|
|
|
|
|
|
EventName = e.EventName,
|
|
|
|
|
|
ProjectName = c.GroupName, // 暂时用 GroupName 代替 ProjectName,具体看 ProjectId 关联
|
|
|
|
|
|
CertificateName = "荣誉证书", // 此时可根据 CertificateType 转义
|
|
|
|
|
|
CertificateUrl = c.CertificateUrl,
|
|
|
|
|
|
IssuedTime = c.IssuedTime
|
|
|
|
|
|
})
|
|
|
|
|
|
.OrderByDescending(c => c.IssuedTime);
|
|
|
|
|
|
|
|
|
|
|
|
return await query.ToPagedListAsync(input);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 我的积分记录
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
[HttpGet("points")]
|
|
|
|
|
|
public async Task<PageResult<MyPointsRecordDto>> GetPoints([FromQuery] MyPageInput input)
|
|
|
|
|
|
{
|
|
|
|
|
|
var userId = GetCurrentUserId();
|
|
|
|
|
|
var query = _pointsRecordRepository.AsQueryable()
|
|
|
|
|
|
.Where(p => p.UserId == userId && !p.DeleteMark)
|
|
|
|
|
|
.Select(p => new MyPointsRecordDto
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = p.Id,
|
|
|
|
|
|
Title = p.Remark ?? (p.Type == PointsOperationType.Acquire ? "获取积分" : "消耗积分"),
|
|
|
|
|
|
Points = p.Points,
|
|
|
|
|
|
Type = (int)p.Type,
|
|
|
|
|
|
CreatedTime = p.CreatedTime
|
|
|
|
|
|
})
|
|
|
|
|
|
.OrderByDescending(p => p.CreatedTime);
|
|
|
|
|
|
|
|
|
|
|
|
return await query.ToPagedListAsync(input);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 我的订单列表
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
[HttpGet("orders")]
|
|
|
|
|
|
public async Task<PageResult<MyOrderDto>> GetOrders([FromQuery] MyPageInput input)
|
|
|
|
|
|
{
|
|
|
|
|
|
var userId = GetCurrentUserId();
|
|
|
|
|
|
var query = _orderRepository.AsQueryable()
|
|
|
|
|
|
.LeftJoin<EventInfo>((o, e) => o.EventId == e.Id)
|
|
|
|
|
|
.Where(o => o.CreatedUserId == userId && !o.DeleteMark)
|
|
|
|
|
|
.Select((o, e) => new MyOrderDto
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = o.Id,
|
|
|
|
|
|
OrderNo = o.OrderNo,
|
|
|
|
|
|
EventName = e.EventName,
|
|
|
|
|
|
Amount = o.ActualAmount,
|
|
|
|
|
|
Status = (int)o.OrderStatus,
|
|
|
|
|
|
CreatedTime = o.CreatedTime
|
|
|
|
|
|
})
|
|
|
|
|
|
.OrderByDescending(o => o.CreatedTime);
|
|
|
|
|
|
|
|
|
|
|
|
return await query.ToPagedListAsync(input);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 我的参赛记录
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
[HttpGet("registrations")]
|
|
|
|
|
|
public async Task<PageResult<MyRegistrationDto>> GetRegistrations([FromQuery] MyPageInput input)
|
|
|
|
|
|
{
|
|
|
|
|
|
var userId = GetCurrentUserId();
|
|
|
|
|
|
var query = _registrationRepository.AsQueryable()
|
|
|
|
|
|
.LeftJoin<EventInfo>((r, e) => r.EventId == e.Id)
|
|
|
|
|
|
.Where(r => r.CreatedUserId == userId && !r.DeleteMark)
|
|
|
|
|
|
.Select((r, e) => new MyRegistrationDto
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = r.Id,
|
|
|
|
|
|
EventName = e.EventName,
|
|
|
|
|
|
TeamName = r.TeamName,
|
|
|
|
|
|
Status = r.RegistrationStatus,
|
|
|
|
|
|
RegistrationTime = r.RegistrationTime,
|
|
|
|
|
|
ProjectNames = "" // 暂空,需联表 EventRegistrationItem
|
|
|
|
|
|
})
|
|
|
|
|
|
.OrderByDescending(r => r.RegistrationTime);
|
|
|
|
|
|
|
|
|
|
|
|
return await query.ToPagedListAsync(input);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private string MaskMobile(string mobile)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrEmpty(mobile) || mobile.Length < 7) return mobile;
|
|
|
|
|
|
return mobile.Substring(0, 3) + "****" + mobile.Substring(7);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|