2026-03-14 06:24:00 +08:00
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
|
|
using YD_Event.Application.Client.Dtos;
|
2026-05-09 14:26:57 +08:00
|
|
|
|
using YD_Event.Application.Common;
|
2026-03-14 06:24:00 +08:00
|
|
|
|
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;
|
2026-04-14 17:30:20 +08:00
|
|
|
|
using EasyCaching.Core;
|
2026-05-09 14:26:57 +08:00
|
|
|
|
using SkiaSharp;
|
2026-04-03 09:42:10 +08:00
|
|
|
|
using ProjectEntity = YD_Event.Core.Entities.Project;
|
2026-03-14 06:24:00 +08:00
|
|
|
|
|
|
|
|
|
|
namespace YD_Event.Application.Client;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 小程序-个人中心接口
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[ApiDescriptionSettings("微信小程序接口")]
|
2026-04-03 09:42:10 +08:00
|
|
|
|
[Authorize]
|
2026-03-14 06:24:00 +08:00
|
|
|
|
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-04-03 09:42:10 +08:00
|
|
|
|
private readonly ISqlSugarRepository<TeamMember> _teamMemberRepository;
|
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;
|
2026-04-03 09:42:10 +08:00
|
|
|
|
private readonly ISqlSugarRepository<EventRegistrationItem> _registrationItemRepository;
|
2026-03-14 06:24:00 +08:00
|
|
|
|
private readonly ISqlSugarRepository<EventInfo> _eventRepository;
|
2026-04-03 09:42:10 +08:00
|
|
|
|
private readonly ISqlSugarRepository<MerchantCertification> _merchantCertificationRepository;
|
2026-05-13 14:04:49 +08:00
|
|
|
|
private readonly ISqlSugarClient _db;
|
2026-03-14 06:24:00 +08:00
|
|
|
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
2026-04-14 17:30:20 +08:00
|
|
|
|
private readonly IEasyCachingProvider _cachingProvider;
|
2026-05-09 14:26:57 +08:00
|
|
|
|
private readonly FileService _fileService;
|
2026-03-14 06:24:00 +08:00
|
|
|
|
|
|
|
|
|
|
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-04-03 09:42:10 +08:00
|
|
|
|
ISqlSugarRepository<TeamMember> teamMemberRepository,
|
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,
|
2026-04-03 09:42:10 +08:00
|
|
|
|
ISqlSugarRepository<EventRegistrationItem> registrationItemRepository,
|
2026-03-14 06:24:00 +08:00
|
|
|
|
ISqlSugarRepository<EventInfo> eventRepository,
|
2026-04-03 09:42:10 +08:00
|
|
|
|
ISqlSugarRepository<MerchantCertification> merchantCertificationRepository,
|
2026-05-13 14:04:49 +08:00
|
|
|
|
ISqlSugarClient db,
|
2026-04-14 17:30:20 +08:00
|
|
|
|
IHttpContextAccessor httpContextAccessor,
|
2026-05-09 14:26:57 +08:00
|
|
|
|
IEasyCachingProvider cachingProvider,
|
|
|
|
|
|
FileService fileService)
|
2026-03-14 06:24:00 +08:00
|
|
|
|
{
|
|
|
|
|
|
_appUserRepository = appUserRepository;
|
|
|
|
|
|
_teamRepository = teamRepository;
|
2026-04-03 09:42:10 +08:00
|
|
|
|
_teamMemberRepository = teamMemberRepository;
|
2026-03-14 06:24:00 +08:00
|
|
|
|
_certificateRepository = certificateRepository;
|
|
|
|
|
|
_pointsRecordRepository = pointsRecordRepository;
|
|
|
|
|
|
_orderRepository = orderRepository;
|
|
|
|
|
|
_registrationRepository = registrationRepository;
|
2026-04-03 09:42:10 +08:00
|
|
|
|
_registrationItemRepository = registrationItemRepository;
|
2026-03-14 06:24:00 +08:00
|
|
|
|
_eventRepository = eventRepository;
|
2026-04-03 09:42:10 +08:00
|
|
|
|
_merchantCertificationRepository = merchantCertificationRepository;
|
2026-05-13 14:04:49 +08:00
|
|
|
|
_db = db;
|
2026-03-14 06:24:00 +08:00
|
|
|
|
_httpContextAccessor = httpContextAccessor;
|
2026-04-14 17:30:20 +08:00
|
|
|
|
_cachingProvider = cachingProvider;
|
2026-05-09 14:26:57 +08:00
|
|
|
|
_fileService = fileService;
|
2026-03-14 06:24:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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();
|
2026-04-03 09:42:10 +08:00
|
|
|
|
var user = await _appUserRepository.GetFirstAsync(u => u.Id == userId);
|
2026-03-14 06:24:00 +08:00
|
|
|
|
if (user == null) throw Oops.Bah("用户信息不存在");
|
|
|
|
|
|
|
|
|
|
|
|
// 统计证书数量
|
2026-04-16 15:42:29 +08:00
|
|
|
|
var certCount = await _certificateRepository.CountAsync(c => c.UserId == userId && c.CertificateStatus == 2);
|
2026-03-14 06:24:00 +08:00
|
|
|
|
|
2026-05-07 14:43:03 +08:00
|
|
|
|
// 小程序端只查看个人积分:UserId = 当前用户且 MerchantCertificationId = null
|
|
|
|
|
|
var pointsIn = await _pointsRecordRepository.AsQueryable().Where(p => p.UserId == userId && p.MerchantCertificationId == null && p.Type == PointsOperationType.Acquire).SumAsync(p => p.Points);
|
|
|
|
|
|
var pointsOut = await _pointsRecordRepository.AsQueryable().Where(p => p.UserId == userId && p.MerchantCertificationId == null && p.Type == PointsOperationType.Consume).SumAsync(p => p.Points);
|
2026-03-14 06:24:00 +08:00
|
|
|
|
var pointsBalance = (int)(pointsIn - pointsOut);
|
|
|
|
|
|
|
2026-04-03 09:42:10 +08:00
|
|
|
|
// 认证状态: 0-待提交, 1-已认证, 2-审核中, 3-失败
|
2026-04-13 15:28:25 +08:00
|
|
|
|
int authStatus = (int)user.AuditStatus;
|
2026-03-14 06:24:00 +08:00
|
|
|
|
|
2026-04-03 09:42:10 +08:00
|
|
|
|
// 获取单位名称
|
|
|
|
|
|
string? merchantName = null;
|
|
|
|
|
|
if (user.MerchantCertificationId.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
var merchantCert = await _merchantCertificationRepository.GetFirstAsync(m => m.Id == user.MerchantCertificationId.Value);
|
|
|
|
|
|
merchantName = merchantCert?.MerchantName;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-14 06:24:00 +08:00
|
|
|
|
return new MyProfileDto
|
|
|
|
|
|
{
|
2026-08-07 14:59:15 +08:00
|
|
|
|
Role = user.MiniProgramRole switch
|
|
|
|
|
|
{
|
|
|
|
|
|
MiniProgramRole.Referee => "referee",
|
|
|
|
|
|
MiniProgramRole.Checker => "checker",
|
|
|
|
|
|
_ => "participant"
|
|
|
|
|
|
},
|
|
|
|
|
|
RoleType = user.MiniProgramRole is MiniProgramRole.Referee or MiniProgramRole.Checker
|
|
|
|
|
|
? (int)user.MiniProgramRole
|
|
|
|
|
|
: (int)MiniProgramRole.Participant,
|
2026-08-24 00:07:15 +08:00
|
|
|
|
RoleName = user.MiniProgramRole switch
|
|
|
|
|
|
{
|
|
|
|
|
|
MiniProgramRole.Referee => "裁判",
|
|
|
|
|
|
MiniProgramRole.Checker => "检录",
|
|
|
|
|
|
_ => user.PersonType == PersonType.Coach ? "教练" : "运动员"
|
|
|
|
|
|
},
|
|
|
|
|
|
PersonType = (int)user.PersonType,
|
2026-03-14 06:24:00 +08:00
|
|
|
|
NickName = user.NickName ?? "微信用户",
|
|
|
|
|
|
Avatar = user.Avatar ?? "",
|
|
|
|
|
|
Mobile = MaskMobile(user.Mobile),
|
|
|
|
|
|
Points = pointsBalance,
|
|
|
|
|
|
CertificateCount = certCount,
|
2026-04-03 09:42:10 +08:00
|
|
|
|
AuthStatus = authStatus,
|
2026-04-04 22:09:43 +08:00
|
|
|
|
MerchantName = merchantName,
|
|
|
|
|
|
Gender = (int?)user.Gender,
|
|
|
|
|
|
BirthDate = user.BirthDate
|
2026-03-14 06:24:00 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取实名认证信息
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
[HttpGet("auth")]
|
|
|
|
|
|
public async Task<MyAuthInfoDto> GetAuthInfo()
|
|
|
|
|
|
{
|
|
|
|
|
|
var userId = GetCurrentUserId();
|
2026-04-03 09:42:10 +08:00
|
|
|
|
var user = await _appUserRepository.GetFirstAsync(u => u.Id == userId);
|
2026-03-14 06:24:00 +08:00
|
|
|
|
if (user == null) throw Oops.Bah("用户信息不存在");
|
|
|
|
|
|
|
2026-06-15 21:51:42 +08:00
|
|
|
|
int authType = user.AuthType;
|
2026-04-03 09:42:10 +08:00
|
|
|
|
|
2026-03-14 06:24:00 +08:00
|
|
|
|
return new MyAuthInfoDto
|
|
|
|
|
|
{
|
2026-04-03 09:42:10 +08:00
|
|
|
|
AuthType = authType,
|
2026-03-14 06:24:00 +08:00
|
|
|
|
Name = user.Name,
|
|
|
|
|
|
Mobile = user.Mobile,
|
2026-04-03 09:42:10 +08:00
|
|
|
|
Gender = (int?)user.Gender,
|
|
|
|
|
|
BirthDate = user.BirthDate,
|
|
|
|
|
|
IdCardNumber = user.IdCardNumber,
|
2026-03-14 06:24:00 +08:00
|
|
|
|
IdCardImageUrl = user.IdCardImageUrl,
|
2026-04-13 15:28:25 +08:00
|
|
|
|
IdCardBackImageUrl = user.IdCardBackImageUrl,
|
2026-06-17 15:42:32 +08:00
|
|
|
|
PassportNumber = user.PassportNumber,
|
2026-04-03 09:42:10 +08:00
|
|
|
|
PassportPhotoUrl = user.PassportPhotoUrl,
|
2026-06-15 21:51:42 +08:00
|
|
|
|
HouseholdPhotoUrl = user.HouseholdPhotoUrl,
|
2026-03-14 06:24:00 +08:00
|
|
|
|
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();
|
2026-04-03 09:42:10 +08:00
|
|
|
|
var user = await _appUserRepository.GetFirstAsync(u => u.Id == userId);
|
2026-03-14 06:24:00 +08:00
|
|
|
|
if (user == null) throw Oops.Bah("用户信息不存在");
|
|
|
|
|
|
|
|
|
|
|
|
if (user.AuditStatus == AuditStatus.PendingAudit) throw Oops.Bah("审核中,请勿重复提交");
|
|
|
|
|
|
|
2026-04-03 09:42:10 +08:00
|
|
|
|
// 验证必填字段
|
|
|
|
|
|
if (input.AuthType == 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrEmpty(input.IdCardNumber)) throw Oops.Bah("身份证号不能为空");
|
|
|
|
|
|
if (string.IsNullOrEmpty(input.IdCardImageUrl)) throw Oops.Bah("身份证正面图片不能为空");
|
2026-04-13 15:28:25 +08:00
|
|
|
|
if (string.IsNullOrEmpty(input.IdCardBackImageUrl)) throw Oops.Bah("身份证背面图片不能为空");
|
2026-06-17 15:42:32 +08:00
|
|
|
|
|
2026-07-20 17:22:42 +08:00
|
|
|
|
// 身份证号查重(排除被单位删除的影子账号:无单位关联且无密码)
|
2026-06-17 16:32:49 +08:00
|
|
|
|
var duplicateIdCard = await _appUserRepository.AsQueryable()
|
2026-07-20 17:22:42 +08:00
|
|
|
|
.Where(u => u.IdCardNumber == input.IdCardNumber && u.Id != userId && !u.DeleteMark && u.PersonType == PersonType.Athlete
|
|
|
|
|
|
&& !(u.MerchantCertificationId == null && u.Password == null))
|
2026-06-17 16:32:49 +08:00
|
|
|
|
.FirstAsync();
|
2026-06-17 15:42:32 +08:00
|
|
|
|
if (duplicateIdCard != null) throw Oops.Bah("该身份证号已被其他用户使用");
|
2026-04-03 09:42:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
else if (input.AuthType == 2)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrEmpty(input.PassportPhotoUrl)) throw Oops.Bah("护照图片不能为空");
|
2026-06-17 15:42:32 +08:00
|
|
|
|
if (string.IsNullOrEmpty(input.PassportNumber)) throw Oops.Bah("护照号码不能为空");
|
2026-04-03 09:42:10 +08:00
|
|
|
|
if (!input.Gender.HasValue) throw Oops.Bah("性别不能为空");
|
|
|
|
|
|
if (!input.BirthDate.HasValue) throw Oops.Bah("出生日期不能为空");
|
2026-06-17 15:42:32 +08:00
|
|
|
|
|
2026-07-20 17:22:42 +08:00
|
|
|
|
// 护照号查重(排除被单位删除的影子账号:无单位关联且无密码)
|
2026-06-17 16:32:49 +08:00
|
|
|
|
var duplicatePassport = await _appUserRepository.AsQueryable()
|
2026-07-20 17:22:42 +08:00
|
|
|
|
.Where(u => u.PassportNumber == input.PassportNumber && u.Id != userId && !u.DeleteMark && u.PersonType == PersonType.Athlete
|
|
|
|
|
|
&& !(u.MerchantCertificationId == null && u.Password == null))
|
2026-06-17 16:32:49 +08:00
|
|
|
|
.FirstAsync();
|
2026-06-17 15:42:32 +08:00
|
|
|
|
if (duplicatePassport != null) throw Oops.Bah("该护照号码已被其他用户使用");
|
2026-04-03 09:42:10 +08:00
|
|
|
|
}
|
2026-06-15 21:51:42 +08:00
|
|
|
|
else if (input.AuthType == 3)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrEmpty(input.HouseholdPhotoUrl)) throw Oops.Bah("户口本图片不能为空");
|
|
|
|
|
|
if (string.IsNullOrEmpty(input.IdCardNumber)) throw Oops.Bah("身份证号不能为空");
|
2026-06-17 15:42:32 +08:00
|
|
|
|
|
2026-07-20 17:22:42 +08:00
|
|
|
|
// 身份证号查重(排除被单位删除的影子账号:无单位关联且无密码)
|
2026-06-17 16:32:49 +08:00
|
|
|
|
var duplicateIdCard = await _appUserRepository.AsQueryable()
|
2026-07-20 17:22:42 +08:00
|
|
|
|
.Where(u => u.IdCardNumber == input.IdCardNumber && u.Id != userId && !u.DeleteMark && u.PersonType == PersonType.Athlete
|
|
|
|
|
|
&& !(u.MerchantCertificationId == null && u.Password == null))
|
2026-06-17 16:32:49 +08:00
|
|
|
|
.FirstAsync();
|
2026-06-17 15:42:32 +08:00
|
|
|
|
if (duplicateIdCard != null) throw Oops.Bah("该身份证号已被其他用户使用");
|
2026-06-15 21:51:42 +08:00
|
|
|
|
}
|
2026-04-03 09:42:10 +08:00
|
|
|
|
else
|
|
|
|
|
|
{
|
2026-06-16 11:44:38 +08:00
|
|
|
|
throw Oops.Bah("请选择认证类型");
|
2026-04-03 09:42:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrEmpty(input.SmsCode)) throw Oops.Bah("手机号验证码不能为空");
|
|
|
|
|
|
|
2026-04-14 17:30:20 +08:00
|
|
|
|
// 1. 验证短信验证码格式(6位数字)
|
|
|
|
|
|
if (input.SmsCode.Length != 6 || !input.SmsCode.All(char.IsDigit))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw Oops.Bah("验证码格式错误");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 从缓存中获取验证码,验证是否为系统最新生成且在有效期内
|
|
|
|
|
|
var codeCacheKey = $"sms:code:{input.Mobile}";
|
|
|
|
|
|
var cachedCode = await _cachingProvider.GetAsync<string>(codeCacheKey);
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 验证验证码是否在有效期内
|
|
|
|
|
|
if (!cachedCode.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw Oops.Bah("验证码已过期");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 验证验证码是否一致
|
|
|
|
|
|
if (cachedCode.Value != input.SmsCode)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw Oops.Bah("验证码不正确");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证成功后,移除验证码缓存
|
|
|
|
|
|
await _cachingProvider.RemoveAsync(codeCacheKey);
|
|
|
|
|
|
|
2026-03-14 06:24:00 +08:00
|
|
|
|
user.Name = input.Name;
|
|
|
|
|
|
user.Mobile = input.Mobile;
|
2026-06-15 21:51:42 +08:00
|
|
|
|
user.AuthType = input.AuthType;
|
2026-04-03 09:42:10 +08:00
|
|
|
|
|
|
|
|
|
|
if (input.AuthType == 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
user.IdCardNumber = input.IdCardNumber;
|
|
|
|
|
|
user.IdCardImageUrl = input.IdCardImageUrl;
|
2026-04-13 15:28:25 +08:00
|
|
|
|
user.IdCardBackImageUrl = input.IdCardBackImageUrl;
|
2026-06-15 21:51:42 +08:00
|
|
|
|
user.PassportPhotoUrl = null;
|
2026-06-17 15:42:32 +08:00
|
|
|
|
user.PassportNumber = null;
|
2026-06-15 21:51:42 +08:00
|
|
|
|
user.HouseholdPhotoUrl = null;
|
2026-04-14 15:11:41 +08:00
|
|
|
|
user.Gender = (Gender)input.Gender.Value;
|
|
|
|
|
|
user.BirthDate = input.BirthDate.Value;
|
2026-04-03 09:42:10 +08:00
|
|
|
|
}
|
2026-06-15 21:51:42 +08:00
|
|
|
|
else if (input.AuthType == 2)
|
2026-04-03 09:42:10 +08:00
|
|
|
|
{
|
2026-06-17 15:42:32 +08:00
|
|
|
|
user.PassportNumber = input.PassportNumber;
|
2026-04-03 09:42:10 +08:00
|
|
|
|
user.PassportPhotoUrl = input.PassportPhotoUrl;
|
2026-06-15 21:51:42 +08:00
|
|
|
|
user.IdCardImageUrl = null;
|
|
|
|
|
|
user.IdCardBackImageUrl = null;
|
2026-06-17 15:42:32 +08:00
|
|
|
|
user.IdCardNumber = null;
|
2026-06-15 21:51:42 +08:00
|
|
|
|
user.HouseholdPhotoUrl = null;
|
2026-04-03 09:42:10 +08:00
|
|
|
|
user.Gender = (Gender)input.Gender.Value;
|
|
|
|
|
|
user.BirthDate = input.BirthDate.Value;
|
|
|
|
|
|
}
|
2026-06-16 11:44:38 +08:00
|
|
|
|
else if (input.AuthType == 3)
|
2026-06-15 21:51:42 +08:00
|
|
|
|
{
|
|
|
|
|
|
// AuthType == 3:户口本认证
|
|
|
|
|
|
user.IdCardNumber = input.IdCardNumber;
|
|
|
|
|
|
user.HouseholdPhotoUrl = input.HouseholdPhotoUrl;
|
|
|
|
|
|
user.IdCardImageUrl = null;
|
|
|
|
|
|
user.IdCardBackImageUrl = null;
|
|
|
|
|
|
user.PassportPhotoUrl = null;
|
2026-06-17 15:42:32 +08:00
|
|
|
|
user.PassportNumber = null;
|
2026-06-15 21:51:42 +08:00
|
|
|
|
if (input.Gender.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
user.Gender = (Gender)input.Gender.Value;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (input.BirthDate.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
user.BirthDate = input.BirthDate.Value;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-16 11:44:38 +08:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// AuthType == 0 或其他值:未认证,不修改任何认证相关字段
|
|
|
|
|
|
user.AuthType = 0;
|
|
|
|
|
|
user.IdCardNumber = null;
|
|
|
|
|
|
user.IdCardImageUrl = null;
|
|
|
|
|
|
user.IdCardBackImageUrl = null;
|
|
|
|
|
|
user.PassportPhotoUrl = null;
|
2026-06-17 15:42:32 +08:00
|
|
|
|
user.PassportNumber = null;
|
2026-06-16 11:44:38 +08:00
|
|
|
|
user.HouseholdPhotoUrl = null;
|
|
|
|
|
|
}
|
2026-04-03 09:42:10 +08:00
|
|
|
|
|
|
|
|
|
|
user.AuditStatus = AuditStatus.PendingAudit;
|
2026-08-30 21:46:05 +08:00
|
|
|
|
user.AuditRemark = null;
|
|
|
|
|
|
user.AuditUserId = null;
|
|
|
|
|
|
user.AuditTime = null;
|
2026-03-14 06:24:00 +08:00
|
|
|
|
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();
|
|
|
|
|
|
|
2026-04-03 09:42:10 +08:00
|
|
|
|
var myTeamIds = await _teamMemberRepository.AsQueryable()
|
|
|
|
|
|
.Where(tm => tm.UserId == userId && !tm.DeleteMark)
|
|
|
|
|
|
.Select(tm => tm.TeamId)
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
2026-03-14 06:24:00 +08:00
|
|
|
|
var query = _teamRepository.AsQueryable()
|
2026-04-03 09:42:10 +08:00
|
|
|
|
.Where(t => (t.CreatedUserId == userId || myTeamIds.Contains(t.Id)) && !t.DeleteMark);
|
|
|
|
|
|
|
|
|
|
|
|
var teams = await query.ToPagedListAsync(input);
|
|
|
|
|
|
|
|
|
|
|
|
var teamIds = teams.Rows.Select(t => t.Id).ToList();
|
|
|
|
|
|
var teamMembers = await _teamMemberRepository.AsQueryable()
|
|
|
|
|
|
.Where(tm => teamIds.Contains(tm.TeamId) && !tm.DeleteMark)
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
var memberCounts = teamMembers
|
2026-05-08 13:04:31 +08:00
|
|
|
|
.Where(tm => tm.PersonType == PersonType.Athlete)
|
2026-04-03 09:42:10 +08:00
|
|
|
|
.GroupBy(tm => tm.TeamId)
|
|
|
|
|
|
.ToDictionary(g => g.Key, g => g.Count());
|
|
|
|
|
|
|
|
|
|
|
|
var result = new PageResult<MyTeamDto>
|
|
|
|
|
|
{
|
|
|
|
|
|
PageNo = teams.PageNo,
|
|
|
|
|
|
PageSize = teams.PageSize,
|
|
|
|
|
|
Total = teams.Total,
|
|
|
|
|
|
Pages = teams.Pages,
|
|
|
|
|
|
Rows = teams.Rows.Select(t => new MyTeamDto
|
2026-03-14 06:24:00 +08:00
|
|
|
|
{
|
|
|
|
|
|
Id = t.Id,
|
|
|
|
|
|
Name = t.Name,
|
2026-04-03 09:42:10 +08:00
|
|
|
|
IsLeader = t.CreatedUserId == userId,
|
|
|
|
|
|
MemberCount = memberCounts.TryGetValue(t.Id, out var count) ? count : 0
|
|
|
|
|
|
}).ToList()
|
|
|
|
|
|
};
|
2026-03-14 06:24:00 +08:00
|
|
|
|
|
2026-04-03 09:42:10 +08:00
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 团队详情
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="id">团队ID</param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
[HttpGet("teams/{id}")]
|
|
|
|
|
|
public async Task<MyTeamDetailDto> GetTeamDetail(long id)
|
|
|
|
|
|
{
|
|
|
|
|
|
var userId = GetCurrentUserId();
|
|
|
|
|
|
|
|
|
|
|
|
var team = await _teamRepository.GetFirstAsync(t => t.Id == id && !t.DeleteMark);
|
|
|
|
|
|
if (team == null) throw Oops.Bah("团队不存在");
|
|
|
|
|
|
|
|
|
|
|
|
var teamMembers = await _teamMemberRepository.AsQueryable()
|
|
|
|
|
|
.Where(tm => tm.TeamId == id && !tm.DeleteMark)
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
if (!teamMembers.Any(tm => tm.UserId == userId) && team.CreatedUserId != userId)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw Oops.Bah("无权访问该团队");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var userIds = teamMembers.Select(tm => tm.UserId).ToList();
|
|
|
|
|
|
var users = await _appUserRepository.AsQueryable()
|
|
|
|
|
|
.Where(u => userIds.Contains(u.Id))
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
var userDict = users.ToDictionary(u => u.Id);
|
|
|
|
|
|
|
|
|
|
|
|
var members = teamMembers.Select(tm =>
|
|
|
|
|
|
{
|
|
|
|
|
|
userDict.TryGetValue(tm.UserId, out var user);
|
2026-04-10 14:29:41 +08:00
|
|
|
|
int? age = null;
|
|
|
|
|
|
if (user?.BirthDate.HasValue == true)
|
|
|
|
|
|
{
|
|
|
|
|
|
var today = DateTime.Today;
|
|
|
|
|
|
age = today.Year - user.BirthDate.Value.Year;
|
|
|
|
|
|
if (user.BirthDate.Value.Date > today.AddYears(-age.Value)) age--;
|
|
|
|
|
|
}
|
2026-04-03 09:42:10 +08:00
|
|
|
|
return new MyTeamMemberDto
|
|
|
|
|
|
{
|
|
|
|
|
|
Name = user?.Name ?? "未知",
|
|
|
|
|
|
Gender = (int)(user?.Gender ?? 0),
|
2026-04-10 14:29:41 +08:00
|
|
|
|
Age = age,
|
2026-04-03 09:42:10 +08:00
|
|
|
|
PersonType = (int)tm.PersonType,
|
|
|
|
|
|
Mobile = user?.Mobile != null ? MaskMobile(user.Mobile) : null
|
|
|
|
|
|
};
|
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
|
|
|
|
var leader = members.FirstOrDefault(m => m.PersonType == 3);
|
|
|
|
|
|
var coach = members.FirstOrDefault(m => m.PersonType == 2);
|
|
|
|
|
|
|
|
|
|
|
|
return new MyTeamDetailDto
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = team.Id,
|
|
|
|
|
|
Name = team.Name,
|
|
|
|
|
|
Leader = leader,
|
|
|
|
|
|
Coach = coach,
|
|
|
|
|
|
CreatedTime = team.CreatedTime,
|
2026-05-08 13:04:31 +08:00
|
|
|
|
MemberCount = members.Count(m => m.PersonType == 1),
|
2026-04-03 09:42:10 +08:00
|
|
|
|
Members = members
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 我的证书总览
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
[HttpGet("certificates/overview")]
|
|
|
|
|
|
public async Task<MyCertificateOverviewDto> GetCertificatesOverview()
|
|
|
|
|
|
{
|
|
|
|
|
|
var userId = GetCurrentUserId();
|
2026-04-16 15:42:29 +08:00
|
|
|
|
var count = await _certificateRepository.CountAsync(c => c.UserId == userId && c.CertificateStatus == 2 && !c.DeleteMark);
|
2026-04-03 09:42:10 +08:00
|
|
|
|
|
|
|
|
|
|
return new MyCertificateOverviewDto
|
|
|
|
|
|
{
|
|
|
|
|
|
TotalCount = count
|
|
|
|
|
|
};
|
2026-03-14 06:24:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 我的证书列表
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
[HttpGet("certificates")]
|
2026-04-03 09:42:10 +08:00
|
|
|
|
public async Task<PageResult<MyCertificateDto>> GetCertificates([FromQuery] MyCertificatePageInput input)
|
2026-03-14 06:24:00 +08:00
|
|
|
|
{
|
|
|
|
|
|
var userId = GetCurrentUserId();
|
|
|
|
|
|
|
2026-04-04 22:09:43 +08:00
|
|
|
|
// 先查询原始数据,然后在内存中转换证书名称
|
2026-03-14 06:24:00 +08:00
|
|
|
|
var query = _certificateRepository.AsQueryable()
|
|
|
|
|
|
.LeftJoin<EventInfo>((c, e) => c.EventId == e.Id)
|
2026-05-08 09:25:07 +08:00
|
|
|
|
.LeftJoin<EventProject>((c, e, ep) => c.ProjectId == ep.Id)
|
|
|
|
|
|
.LeftJoin<ProjectEntity>((c, e, ep, p) => ep.ProjectId == p.Id)
|
2026-05-08 13:04:31 +08:00
|
|
|
|
.Where((c, e, ep, p) => c.UserId == userId && c.CertificateStatus == 2 && !c.DeleteMark);
|
|
|
|
|
|
|
|
|
|
|
|
// 根据类型筛选
|
|
|
|
|
|
if (input.Type.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (input.Type == 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 个人证书:没有单位认证ID
|
|
|
|
|
|
query = query.Where((c, e, ep, p) => !c.MerchantCertificationId.HasValue);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (input.Type == 2)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 团队证书:有单位认证ID
|
|
|
|
|
|
query = query.Where((c, e, ep, p) => c.MerchantCertificationId.HasValue);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var resultQuery = query.Select((c, e, ep, p) => new {
|
2026-03-14 06:24:00 +08:00
|
|
|
|
Id = c.Id,
|
|
|
|
|
|
EventName = e.EventName,
|
2026-04-03 09:42:10 +08:00
|
|
|
|
ProjectName = p.Name,
|
|
|
|
|
|
GroupName = c.GroupName,
|
2026-04-04 22:09:43 +08:00
|
|
|
|
CertificateType = c.CertificateType,
|
2026-03-14 06:24:00 +08:00
|
|
|
|
CertificateUrl = c.CertificateUrl,
|
|
|
|
|
|
IssuedTime = c.IssuedTime
|
|
|
|
|
|
})
|
|
|
|
|
|
.OrderByDescending(c => c.IssuedTime);
|
|
|
|
|
|
|
2026-05-08 13:04:31 +08:00
|
|
|
|
var pagedResult = await resultQuery.ToPagedListAsync(input);
|
2026-04-04 22:09:43 +08:00
|
|
|
|
|
|
|
|
|
|
// 在内存中转换证书名称
|
|
|
|
|
|
var result = new PageResult<MyCertificateDto>
|
|
|
|
|
|
{
|
|
|
|
|
|
PageNo = pagedResult.PageNo,
|
|
|
|
|
|
PageSize = pagedResult.PageSize,
|
|
|
|
|
|
Total = pagedResult.Total,
|
|
|
|
|
|
Pages = pagedResult.Pages,
|
|
|
|
|
|
Rows = pagedResult.Rows.Select(x => new MyCertificateDto
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = x.Id,
|
|
|
|
|
|
EventName = x.EventName,
|
|
|
|
|
|
ProjectName = x.ProjectName,
|
|
|
|
|
|
GroupName = x.GroupName,
|
|
|
|
|
|
CertificateName = GetCertificateName(x.CertificateType),
|
|
|
|
|
|
CertificateUrl = x.CertificateUrl,
|
|
|
|
|
|
IssuedTime = x.IssuedTime
|
|
|
|
|
|
}).ToList()
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
2026-03-14 06:24:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-03 09:42:10 +08:00
|
|
|
|
private string GetCertificateName(int certificateType)
|
|
|
|
|
|
{
|
|
|
|
|
|
return certificateType switch
|
|
|
|
|
|
{
|
|
|
|
|
|
1 => "冠军证书",
|
|
|
|
|
|
2 => "亚军证书",
|
|
|
|
|
|
3 => "季军证书",
|
|
|
|
|
|
4 => "优胜证书",
|
|
|
|
|
|
5 => "参与证书",
|
|
|
|
|
|
_ => "荣誉证书"
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 我的积分总览
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
[HttpGet("points/overview")]
|
|
|
|
|
|
public async Task<MyPointsOverviewDto> GetPointsOverview()
|
|
|
|
|
|
{
|
|
|
|
|
|
var userId = GetCurrentUserId();
|
|
|
|
|
|
|
2026-05-07 14:43:03 +08:00
|
|
|
|
// 小程序端只查看个人积分:UserId = 当前用户且 MerchantCertificationId = null
|
|
|
|
|
|
var pointsIn = await _pointsRecordRepository.AsQueryable().Where(p => p.UserId == userId && p.MerchantCertificationId == null && p.Type == PointsOperationType.Acquire).SumAsync(p => p.Points);
|
|
|
|
|
|
var pointsOut = await _pointsRecordRepository.AsQueryable().Where(p => p.UserId == userId && p.MerchantCertificationId == null && p.Type == PointsOperationType.Consume).SumAsync(p => p.Points);
|
2026-04-03 09:42:10 +08:00
|
|
|
|
var pointsBalance = (int)(pointsIn - pointsOut);
|
|
|
|
|
|
|
|
|
|
|
|
return new MyPointsOverviewDto
|
|
|
|
|
|
{
|
|
|
|
|
|
TotalPoints = pointsBalance
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-14 06:24:00 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 我的积分记录
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
[HttpGet("points")]
|
2026-04-03 09:42:10 +08:00
|
|
|
|
public async Task<PageResult<MyPointsRecordDto>> GetPoints([FromQuery] MyPointsPageInput input)
|
2026-03-14 06:24:00 +08:00
|
|
|
|
{
|
|
|
|
|
|
var userId = GetCurrentUserId();
|
|
|
|
|
|
var query = _pointsRecordRepository.AsQueryable()
|
2026-04-03 09:42:10 +08:00
|
|
|
|
.LeftJoin<EventInfo>((p, e) => p.EventId == e.Id)
|
2026-05-08 09:25:07 +08:00
|
|
|
|
.LeftJoin<EventProject>((p, e, ep) => p.ProjectId == ep.Id)
|
|
|
|
|
|
.LeftJoin<ProjectEntity>((p, e, ep, pr) => ep.ProjectId == pr.Id)
|
|
|
|
|
|
.LeftJoin<EventProjectGroup>((p, e, ep, pr, pg) => p.ProjectGroupId == pg.Id)
|
2026-05-08 10:54:08 +08:00
|
|
|
|
.LeftJoin<EventResult>((p, e, ep, pr, pg, r) => p.ResultId == r.Id && !r.DeleteMark)
|
2026-05-07 14:43:03 +08:00
|
|
|
|
// 小程序端只查看个人积分:UserId = 当前用户且 MerchantCertificationId = null
|
2026-05-08 10:54:08 +08:00
|
|
|
|
.Where((p, e, ep, pr, pg, r) => p.UserId == userId && p.MerchantCertificationId == null && !p.DeleteMark);
|
2026-04-03 09:42:10 +08:00
|
|
|
|
|
|
|
|
|
|
if (input.Type.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
var typeFilter = (PointsOperationType)input.Type.Value;
|
2026-05-08 10:54:08 +08:00
|
|
|
|
query = query.Where((p, e, ep, pr, pg, r) => p.Type == typeFilter);
|
2026-04-03 09:42:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-07 14:43:03 +08:00
|
|
|
|
// 如果前端指定了 MerchantCertificationId,优先使用前端的(保持向后兼容)
|
2026-04-03 09:42:10 +08:00
|
|
|
|
if (input.MerchantCertificationId.HasValue)
|
|
|
|
|
|
{
|
2026-05-08 10:54:08 +08:00
|
|
|
|
query = query.Where((p, e, ep, pr, pg, r) => p.MerchantCertificationId == input.MerchantCertificationId.Value);
|
2026-04-03 09:42:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-08 10:54:08 +08:00
|
|
|
|
var resultQuery = query.Select((p, e, ep, pr, pg, r) => new MyPointsRecordDto
|
2026-03-14 06:24:00 +08:00
|
|
|
|
{
|
|
|
|
|
|
Id = p.Id,
|
|
|
|
|
|
Title = p.Remark ?? (p.Type == PointsOperationType.Acquire ? "获取积分" : "消耗积分"),
|
2026-04-03 09:42:10 +08:00
|
|
|
|
EventName = e.EventName,
|
|
|
|
|
|
ProjectName = pr.Name,
|
|
|
|
|
|
GroupName = pg.GroupName,
|
2026-03-14 06:24:00 +08:00
|
|
|
|
Points = p.Points,
|
|
|
|
|
|
Type = (int)p.Type,
|
2026-04-03 09:42:10 +08:00
|
|
|
|
CreatedTime = p.CreatedTime,
|
2026-05-08 10:54:08 +08:00
|
|
|
|
Rank = r.Rank > 0 ? r.Rank : null // 从成绩记录中获取名次
|
2026-03-14 06:24:00 +08:00
|
|
|
|
})
|
|
|
|
|
|
.OrderByDescending(p => p.CreatedTime);
|
|
|
|
|
|
|
2026-04-03 09:42:10 +08:00
|
|
|
|
return await resultQuery.ToPagedListAsync(input);
|
2026-03-14 06:24:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 我的订单列表
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
[HttpGet("orders")]
|
|
|
|
|
|
public async Task<PageResult<MyOrderDto>> GetOrders([FromQuery] MyPageInput input)
|
|
|
|
|
|
{
|
|
|
|
|
|
var userId = GetCurrentUserId();
|
2026-05-12 22:54:27 +08:00
|
|
|
|
|
2026-05-15 11:07:00 +08:00
|
|
|
|
// 首先获取用户自己创建的订单ID列表
|
|
|
|
|
|
var userOrderIds = await _orderRepository.AsQueryable()
|
|
|
|
|
|
.Where(o => o.CreatedUserId == userId && !o.DeleteMark)
|
|
|
|
|
|
.Select(o => o.Id)
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
// 查询订单:包括用户自己创建的订单,以及关联到用户订单的退款订单
|
2026-05-12 22:54:27 +08:00
|
|
|
|
var orders = await _orderRepository.AsQueryable()
|
2026-03-14 06:24:00 +08:00
|
|
|
|
.LeftJoin<EventInfo>((o, e) => o.EventId == e.Id)
|
2026-05-15 11:07:00 +08:00
|
|
|
|
.Where((o, e) =>
|
|
|
|
|
|
!o.DeleteMark &&
|
|
|
|
|
|
o.OrderStatus != OrderStatus.Cancelled &&
|
|
|
|
|
|
(o.CreatedUserId == userId ||
|
|
|
|
|
|
(o.OrderType == 2 && o.RelatedOrderId.HasValue && userOrderIds.Contains(o.RelatedOrderId.Value)))
|
|
|
|
|
|
)
|
2026-05-12 22:54:27 +08:00
|
|
|
|
.Select((o, e) => new
|
2026-03-14 06:24:00 +08:00
|
|
|
|
{
|
2026-05-12 22:54:27 +08:00
|
|
|
|
o.Id,
|
|
|
|
|
|
o.OrderNo,
|
2026-03-14 06:24:00 +08:00
|
|
|
|
EventName = e.EventName,
|
2026-05-12 22:54:27 +08:00
|
|
|
|
o.ActualAmount,
|
|
|
|
|
|
o.OrderType,
|
|
|
|
|
|
o.RelatedOrderId,
|
|
|
|
|
|
o.OrderStatus,
|
|
|
|
|
|
o.PaymentTime,
|
|
|
|
|
|
o.CreatedTime
|
2026-03-14 06:24:00 +08:00
|
|
|
|
})
|
2026-05-12 22:54:27 +08:00
|
|
|
|
.OrderByDescending(o => o.CreatedTime)
|
|
|
|
|
|
.ToPagedListAsync(input);
|
|
|
|
|
|
|
|
|
|
|
|
// 处理退款订单的金额显示
|
|
|
|
|
|
var resultList = new List<MyOrderDto>();
|
|
|
|
|
|
foreach (var order in orders.Rows)
|
|
|
|
|
|
{
|
|
|
|
|
|
var dto = new MyOrderDto
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = order.Id,
|
|
|
|
|
|
OrderNo = order.OrderNo,
|
|
|
|
|
|
EventName = order.EventName,
|
|
|
|
|
|
ActualAmount = order.ActualAmount,
|
|
|
|
|
|
RefundAmount = order.OrderType == 2 ? order.ActualAmount : null,
|
|
|
|
|
|
OrderStatus = order.OrderStatus,
|
|
|
|
|
|
PaymentTime = order.PaymentTime,
|
|
|
|
|
|
CreatedTime = order.CreatedTime
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是退款订单,查找关联的原始订单,显示原始支付金额
|
|
|
|
|
|
if (order.OrderType == 2 && order.RelatedOrderId > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
var originalOrder = await _orderRepository.GetByIdAsync(order.RelatedOrderId);
|
|
|
|
|
|
if (originalOrder != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
dto.ActualAmount = originalOrder.ActualAmount;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
resultList.Add(dto);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return new PageResult<MyOrderDto>
|
|
|
|
|
|
{
|
|
|
|
|
|
PageNo = orders.PageNo,
|
|
|
|
|
|
PageSize = orders.PageSize,
|
|
|
|
|
|
Total = orders.Total,
|
|
|
|
|
|
Rows = resultList
|
|
|
|
|
|
};
|
2026-03-14 06:24:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-04-03 09:42:10 +08:00
|
|
|
|
/// 订单详情
|
2026-03-14 06:24:00 +08:00
|
|
|
|
/// </summary>
|
2026-04-03 09:42:10 +08:00
|
|
|
|
/// <param name="id">订单ID</param>
|
2026-03-14 06:24:00 +08:00
|
|
|
|
/// <returns></returns>
|
2026-04-03 09:42:10 +08:00
|
|
|
|
[HttpGet("orders/{id}")]
|
|
|
|
|
|
public async Task<MyOrderDetailDto> GetOrderDetail(long id)
|
|
|
|
|
|
{
|
|
|
|
|
|
var userId = GetCurrentUserId();
|
|
|
|
|
|
|
2026-04-14 11:18:15 +08:00
|
|
|
|
// 1. 先查询订单
|
2026-05-15 11:07:00 +08:00
|
|
|
|
var order = await _orderRepository.GetFirstAsync(o => o.Id == id && !o.DeleteMark);
|
2026-04-14 11:18:15 +08:00
|
|
|
|
if (order == null) throw Oops.Bah("订单不存在");
|
2026-04-03 09:42:10 +08:00
|
|
|
|
|
2026-05-13 14:04:49 +08:00
|
|
|
|
// 2. 查询关联的报名信息,包括软删除的
|
2026-04-14 11:18:15 +08:00
|
|
|
|
EventRegistration? registration = null;
|
|
|
|
|
|
if (order.RegistrationId > 0)
|
|
|
|
|
|
{
|
2026-05-13 14:04:49 +08:00
|
|
|
|
registration = await _db.Queryable<EventRegistration>()
|
|
|
|
|
|
.ClearFilter()
|
|
|
|
|
|
.Where(r => r.Id == order.RegistrationId)
|
|
|
|
|
|
.FirstAsync();
|
2026-04-14 11:18:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 16:13:01 +08:00
|
|
|
|
// 3. 获取赛事信息和名称
|
|
|
|
|
|
var eventQuery = await _eventRepository.AsQueryable()
|
2026-04-14 11:18:15 +08:00
|
|
|
|
.Where(e => e.Id == order.EventId)
|
2026-05-13 16:13:01 +08:00
|
|
|
|
.Select(e => new { e.EventName, e.ChargeMode })
|
2026-04-03 09:42:10 +08:00
|
|
|
|
.FirstAsync();
|
2026-05-13 16:13:01 +08:00
|
|
|
|
var eventName = eventQuery.EventName;
|
|
|
|
|
|
var chargeMode = eventQuery.ChargeMode;
|
2026-04-03 09:42:10 +08:00
|
|
|
|
|
2026-05-13 17:23:27 +08:00
|
|
|
|
// 4. 获取项目列表
|
2026-04-14 11:18:15 +08:00
|
|
|
|
List<MyOrderItemDto> items = new List<MyOrderItemDto>();
|
2026-05-13 16:13:01 +08:00
|
|
|
|
|
2026-05-13 17:23:27 +08:00
|
|
|
|
if (order.RegistrationId > 0)
|
2026-04-14 11:18:15 +08:00
|
|
|
|
{
|
2026-05-13 17:23:27 +08:00
|
|
|
|
if (order.OrderType == 2)
|
2026-05-13 14:04:49 +08:00
|
|
|
|
{
|
2026-05-13 17:23:27 +08:00
|
|
|
|
// 退款订单:通过 RefundOrderId 字段精准找到对应的软删除记录
|
|
|
|
|
|
var refundOrderId = order.Id;
|
|
|
|
|
|
|
|
|
|
|
|
// 先找被软删除的人员中 RefundOrderId 等于当前退款订单ID的
|
|
|
|
|
|
var deletedPersons = await _db.Queryable<EventRegistrationPerson>()
|
|
|
|
|
|
.ClearFilter()
|
|
|
|
|
|
.Where(p => p.RegistrationId == order.RegistrationId &&
|
|
|
|
|
|
p.DeleteMark == true &&
|
|
|
|
|
|
p.RefundOrderId == refundOrderId)
|
|
|
|
|
|
.Select(p => p.RegistrationItemId)
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
if (deletedPersons.Any())
|
2026-05-13 14:04:49 +08:00
|
|
|
|
{
|
2026-05-13 17:23:27 +08:00
|
|
|
|
// 找出这些人员关联的项目(去重)
|
|
|
|
|
|
var distinctItemIds = deletedPersons.Distinct().ToList();
|
|
|
|
|
|
items = await _db.Queryable<EventRegistrationItem>()
|
2026-05-13 14:04:49 +08:00
|
|
|
|
.ClearFilter()
|
2026-05-13 17:23:27 +08:00
|
|
|
|
.Where(i => distinctItemIds.Contains(i.Id))
|
|
|
|
|
|
.Select(i => new MyOrderItemDto
|
|
|
|
|
|
{
|
|
|
|
|
|
ProjectName = i.ProjectName,
|
|
|
|
|
|
GroupName = i.GroupName,
|
|
|
|
|
|
Fee = i.TotalPrice
|
|
|
|
|
|
})
|
2026-05-13 14:04:49 +08:00
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
2026-04-14 11:18:15 +08:00
|
|
|
|
{
|
2026-05-13 17:23:27 +08:00
|
|
|
|
// 如果没有找到匹配的人员,再试试找项目的 RefundOrderId 等于当前退款订单ID的
|
2026-05-13 14:04:49 +08:00
|
|
|
|
items = await _db.Queryable<EventRegistrationItem>()
|
|
|
|
|
|
.ClearFilter()
|
2026-05-13 17:23:27 +08:00
|
|
|
|
.Where(i => i.RegistrationId == order.RegistrationId &&
|
|
|
|
|
|
i.DeleteMark == true &&
|
|
|
|
|
|
i.RefundOrderId == refundOrderId)
|
2026-05-13 14:04:49 +08:00
|
|
|
|
.Select(i => new MyOrderItemDto
|
|
|
|
|
|
{
|
|
|
|
|
|
ProjectName = i.ProjectName,
|
|
|
|
|
|
GroupName = i.GroupName,
|
|
|
|
|
|
Fee = i.TotalPrice
|
|
|
|
|
|
})
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-13 17:23:27 +08:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 其他订单:显示全部的项目
|
|
|
|
|
|
items = await _db.Queryable<EventRegistrationItem>()
|
|
|
|
|
|
.ClearFilter()
|
|
|
|
|
|
.Where(i => i.RegistrationId == order.RegistrationId)
|
|
|
|
|
|
.Select(i => new MyOrderItemDto
|
|
|
|
|
|
{
|
|
|
|
|
|
ProjectName = i.ProjectName,
|
|
|
|
|
|
GroupName = i.GroupName,
|
|
|
|
|
|
Fee = i.TotalPrice
|
|
|
|
|
|
})
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
}
|
2026-04-14 11:18:15 +08:00
|
|
|
|
}
|
2026-04-03 09:42:10 +08:00
|
|
|
|
|
2026-05-12 22:54:27 +08:00
|
|
|
|
// 如果是退款订单,查找关联的原始订单,显示原始支付金额
|
|
|
|
|
|
decimal displayAmount = order.ActualAmount;
|
|
|
|
|
|
if (order.OrderType == 2 && order.RelatedOrderId > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
var originalOrder = await _orderRepository.GetByIdAsync(order.RelatedOrderId);
|
|
|
|
|
|
if (originalOrder != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
displayAmount = originalOrder.ActualAmount;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-03 09:42:10 +08:00
|
|
|
|
return new MyOrderDetailDto
|
|
|
|
|
|
{
|
2026-04-14 11:18:15 +08:00
|
|
|
|
Id = order.Id,
|
|
|
|
|
|
OrderNo = order.OrderNo,
|
2026-04-03 09:42:10 +08:00
|
|
|
|
EventName = eventName,
|
2026-05-12 22:54:27 +08:00
|
|
|
|
ActualAmount = displayAmount,
|
2026-04-14 11:18:15 +08:00
|
|
|
|
RefundAmount = order.OrderType == 2 ? order.ActualAmount : null,
|
|
|
|
|
|
RefundTime = order.OrderType == 2 ? order.PaymentTime : null,
|
|
|
|
|
|
OrderStatus = order.OrderStatus,
|
|
|
|
|
|
PaymentTime = order.PaymentTime,
|
|
|
|
|
|
PaymentMethod = registration?.PaymentMethod ?? 0,
|
2026-05-13 17:23:27 +08:00
|
|
|
|
ChargeMode = chargeMode,
|
2026-04-03 09:42:10 +08:00
|
|
|
|
Items = items
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-04-14 11:18:15 +08:00
|
|
|
|
/// 我的参赛记录
|
2026-04-03 09:42:10 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
2026-04-14 11:18:15 +08:00
|
|
|
|
[HttpGet("registrations")]
|
|
|
|
|
|
[DisplayName("我的参赛记录")]
|
2026-03-14 06:24:00 +08:00
|
|
|
|
public async Task<PageResult<MyRegistrationDto>> GetRegistrations([FromQuery] MyPageInput input)
|
|
|
|
|
|
{
|
|
|
|
|
|
var userId = GetCurrentUserId();
|
2026-04-14 11:18:15 +08:00
|
|
|
|
|
|
|
|
|
|
// 1. 先分页查询报名记录
|
|
|
|
|
|
var regQuery = _registrationRepository.AsQueryable()
|
2026-05-12 10:51:12 +08:00
|
|
|
|
.Where(r => r.CreatedUserId == userId && !r.DeleteMark &&
|
|
|
|
|
|
((r.PaymentMethod == 2 && r.PaymentStatus == 1) || (r.PaymentMethod == 1 && (r.PaymentStatus == 0 || r.PaymentStatus == 1))))
|
2026-04-14 11:18:15 +08:00
|
|
|
|
.OrderByDescending(r => r.RegistrationTime);
|
|
|
|
|
|
|
|
|
|
|
|
var pagedRegs = await regQuery.ToPagedListAsync(input);
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 获取所有赛事ID,然后批量查询赛事信息
|
|
|
|
|
|
var eventIds = pagedRegs.Rows.Select(r => r.EventId).Distinct().ToList();
|
|
|
|
|
|
var eventList = await _eventRepository.AsQueryable()
|
|
|
|
|
|
.Where(e => eventIds.Contains(e.Id))
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
var eventsDict = eventList.ToDictionary(e => e.Id, e => e.EventName);
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 构建 DTO 列表
|
|
|
|
|
|
var result = new PageResult<MyRegistrationDto>
|
|
|
|
|
|
{
|
|
|
|
|
|
PageNo = pagedRegs.PageNo,
|
|
|
|
|
|
PageSize = pagedRegs.PageSize,
|
|
|
|
|
|
Total = pagedRegs.Total,
|
|
|
|
|
|
Pages = pagedRegs.Pages,
|
|
|
|
|
|
Rows = pagedRegs.Rows.Select(r => new MyRegistrationDto
|
2026-03-14 06:24:00 +08:00
|
|
|
|
{
|
|
|
|
|
|
Id = r.Id,
|
2026-04-14 11:18:15 +08:00
|
|
|
|
EventName = eventsDict.ContainsKey(r.EventId) ? eventsDict[r.EventId] : "",
|
2026-03-14 06:24:00 +08:00
|
|
|
|
TeamName = r.TeamName,
|
2026-04-09 23:12:51 +08:00
|
|
|
|
Status = r.PaymentStatus,
|
2026-03-14 06:24:00 +08:00
|
|
|
|
RegistrationTime = r.RegistrationTime,
|
2026-04-03 09:42:10 +08:00
|
|
|
|
ProjectNames = ""
|
2026-04-14 11:18:15 +08:00
|
|
|
|
}).ToList()
|
|
|
|
|
|
};
|
2026-03-14 06:24:00 +08:00
|
|
|
|
|
2026-04-14 11:18:15 +08:00
|
|
|
|
// 4. 填充项目名称
|
2026-04-03 09:42:10 +08:00
|
|
|
|
if (result.Rows.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
var registrationIds = result.Rows.Select(r => r.Id).ToList();
|
|
|
|
|
|
var items = await _registrationItemRepository.AsQueryable()
|
|
|
|
|
|
.Where(i => registrationIds.Contains(i.RegistrationId) && !i.DeleteMark)
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
var itemsByRegistrationId = items.GroupBy(i => i.RegistrationId)
|
|
|
|
|
|
.ToDictionary(g => g.Key, g => string.Join("、", g.Select(i => i.ProjectName)));
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var row in result.Rows)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (itemsByRegistrationId.TryGetValue(row.Id, out var projectNames))
|
|
|
|
|
|
{
|
|
|
|
|
|
row.ProjectNames = projectNames;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
2026-03-14 06:24:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private string MaskMobile(string mobile)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrEmpty(mobile) || mobile.Length < 7) return mobile;
|
|
|
|
|
|
return mobile.Substring(0, 3) + "****" + mobile.Substring(7);
|
|
|
|
|
|
}
|
2026-05-09 14:26:57 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 更新头像 - 上传图片文件
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="file">头像图片文件</param>
|
|
|
|
|
|
/// <returns>更新后的头像信息</returns>
|
|
|
|
|
|
[HttpPost("avatar/upload")]
|
|
|
|
|
|
[DisplayName("上传头像")]
|
|
|
|
|
|
public async Task<UpdateAvatarOutput> UpdateAvatarByFile(IFormFile file)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (file == null || file.Length == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw Oops.Bah("请上传头像图片");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证文件类型
|
|
|
|
|
|
var allowedExtensions = new[] { ".jpg", ".jpeg", ".png", ".gif", ".bmp" };
|
|
|
|
|
|
var extension = Path.GetExtension(file.FileName).ToLower();
|
|
|
|
|
|
if (!allowedExtensions.Contains(extension))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw Oops.Bah("仅支持 JPG、JPEG、PNG、GIF、BMP 格式的图片");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证文件大小 (最大 5MB)
|
|
|
|
|
|
if (file.Length > 5 * 1024 * 1024)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw Oops.Bah("图片大小不能超过 5MB");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理图片
|
|
|
|
|
|
using var processedStream = await ProcessAvatarImage(file.OpenReadStream());
|
|
|
|
|
|
|
|
|
|
|
|
// 上传处理后的图片
|
|
|
|
|
|
var now = DateTime.Now;
|
|
|
|
|
|
var fileName = $"avatar/{now.Year}/{now.Month:D2}/{now.Day:D2}/{Yitter.IdGenerator.YitIdHelper.NextId()}.jpg";
|
|
|
|
|
|
var avatarUrl = await _fileService.UploadFileStreamAsync(fileName, processedStream);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新用户头像
|
|
|
|
|
|
var userId = GetCurrentUserId();
|
|
|
|
|
|
var user = await _appUserRepository.GetFirstAsync(u => u.Id == userId);
|
|
|
|
|
|
if (user == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw Oops.Bah("用户信息不存在");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
user.Avatar = avatarUrl;
|
|
|
|
|
|
user.UpdatedTime = DateTime.Now;
|
|
|
|
|
|
await _appUserRepository.UpdateAsync(user);
|
|
|
|
|
|
|
|
|
|
|
|
return new UpdateAvatarOutput
|
|
|
|
|
|
{
|
|
|
|
|
|
AvatarUrl = avatarUrl
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 更新头像 - 使用图片URL
|
|
|
|
|
|
/// </summary>
|
2026-05-09 15:29:42 +08:00
|
|
|
|
/// <param name="input">头像URL输入</param>
|
2026-05-09 14:26:57 +08:00
|
|
|
|
/// <returns>更新后的头像信息</returns>
|
|
|
|
|
|
[HttpPost("avatar/url")]
|
|
|
|
|
|
[DisplayName("更新头像URL")]
|
2026-05-09 15:29:42 +08:00
|
|
|
|
public async Task<UpdateAvatarOutput> UpdateAvatarByUrl([FromBody] UpdateAvatarUrlInput input)
|
2026-05-09 14:26:57 +08:00
|
|
|
|
{
|
|
|
|
|
|
// 验证URL格式
|
2026-05-09 15:29:42 +08:00
|
|
|
|
if (!Uri.TryCreate(input.AvatarUrl, UriKind.Absolute, out _))
|
2026-05-09 14:26:57 +08:00
|
|
|
|
{
|
|
|
|
|
|
throw Oops.Bah("头像URL格式不正确");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新用户头像
|
|
|
|
|
|
var userId = GetCurrentUserId();
|
|
|
|
|
|
var user = await _appUserRepository.GetFirstAsync(u => u.Id == userId);
|
|
|
|
|
|
if (user == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw Oops.Bah("用户信息不存在");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 15:29:42 +08:00
|
|
|
|
user.Avatar = input.AvatarUrl;
|
2026-05-09 14:26:57 +08:00
|
|
|
|
user.UpdatedTime = DateTime.Now;
|
|
|
|
|
|
await _appUserRepository.UpdateAsync(user);
|
|
|
|
|
|
|
|
|
|
|
|
return new UpdateAvatarOutput
|
|
|
|
|
|
{
|
2026-05-09 15:29:42 +08:00
|
|
|
|
AvatarUrl = input.AvatarUrl
|
2026-05-09 14:26:57 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 处理头像图片 - 压缩和格式转换
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="inputStream">原始图片流</param>
|
|
|
|
|
|
/// <returns>处理后的图片流</returns>
|
|
|
|
|
|
private async Task<Stream> ProcessAvatarImage(Stream inputStream)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 将流复制到内存流,以便可以多次读取
|
|
|
|
|
|
var memoryStream = new MemoryStream();
|
|
|
|
|
|
await inputStream.CopyToAsync(memoryStream);
|
|
|
|
|
|
memoryStream.Position = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 使用 SkiaSharp 处理图片
|
|
|
|
|
|
using var originalBitmap = SKBitmap.Decode(memoryStream);
|
|
|
|
|
|
if (originalBitmap == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw Oops.Bah("图片解码失败,请检查图片格式");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 计算缩放后的尺寸(最大 200x200)
|
|
|
|
|
|
const int maxSize = 200;
|
|
|
|
|
|
int width = originalBitmap.Width;
|
|
|
|
|
|
int height = originalBitmap.Height;
|
|
|
|
|
|
|
|
|
|
|
|
if (width > maxSize || height > maxSize)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (width > height)
|
|
|
|
|
|
{
|
|
|
|
|
|
height = (int)(height * (double)maxSize / width);
|
|
|
|
|
|
width = maxSize;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
width = (int)(width * (double)maxSize / height);
|
|
|
|
|
|
height = maxSize;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建缩放后的图片
|
|
|
|
|
|
using var scaledBitmap = originalBitmap.Resize(new SKImageInfo(width, height), SKFilterQuality.High);
|
|
|
|
|
|
|
|
|
|
|
|
// 裁剪为正方形(从中心裁剪)
|
|
|
|
|
|
int cropSize = Math.Min(width, height);
|
|
|
|
|
|
int cropX = (width - cropSize) / 2;
|
|
|
|
|
|
int cropY = (height - cropSize) / 2;
|
|
|
|
|
|
|
|
|
|
|
|
using var croppedBitmap = new SKBitmap(cropSize, cropSize);
|
|
|
|
|
|
using var canvas = new SKCanvas(croppedBitmap);
|
|
|
|
|
|
canvas.Clear(SKColors.Transparent);
|
|
|
|
|
|
canvas.DrawBitmap(scaledBitmap, new SKRect(cropX, cropY, cropX + cropSize, cropY + cropSize), new SKRect(0, 0, cropSize, cropSize));
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为 JPG 格式并压缩
|
|
|
|
|
|
var outputStream = new MemoryStream();
|
|
|
|
|
|
using var image = SKImage.FromBitmap(croppedBitmap);
|
|
|
|
|
|
using var data = image.Encode(SKEncodedImageFormat.Jpeg, 80); // 80% 质量
|
|
|
|
|
|
data.SaveTo(outputStream);
|
|
|
|
|
|
outputStream.Position = 0;
|
|
|
|
|
|
|
|
|
|
|
|
return outputStream;
|
|
|
|
|
|
}
|
2026-03-14 06:24:00 +08:00
|
|
|
|
}
|