352 lines
13 KiB
C#
Raw Normal View History

2026-03-06 12:10:09 +08:00
using YD_Event.Application.AppUser.Dtos;
using YD_Event.Application.Auth;
using YD_Event.Core.Entities;
using AppUserEntity = YD_Event.Core.Entities.AppUser;
2026-03-12 10:30:13 +08:00
using YD_Event.Core.Enum;
using Mapster;
using EasyCaching.Core;
2026-03-06 12:10:09 +08:00
namespace YD_Event.Application.AppUser;
/// <summary>
/// 普通用户服务
/// </summary>
public class AppUserService : BaseService<AppUserEntity>, ITransient
{
private readonly ISqlSugarRepository<AppUserEntity> _appUserRep;
2026-03-11 16:00:09 +08:00
private readonly ISqlSugarClient _db;
2026-03-06 12:10:09 +08:00
private readonly AuthManager _authManager;
private readonly IEasyCachingProvider _cachingProvider;
2026-03-06 12:10:09 +08:00
public AppUserService(ISqlSugarRepository<AppUserEntity> appUserRep,
2026-03-11 16:00:09 +08:00
ISqlSugarClient db,
AuthManager authManager,
IEasyCachingProvider cachingProvider) : base(appUserRep)
2026-03-06 12:10:09 +08:00
{
_appUserRep = appUserRep;
2026-03-11 16:00:09 +08:00
_db = db;
2026-03-06 12:10:09 +08:00
_authManager = authManager;
_cachingProvider = cachingProvider;
2026-03-06 12:10:09 +08:00
}
/// <summary>
/// 分页查询普通用户
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
2026-03-17 15:32:01 +08:00
[DisplayName("分页查询普通用户")]
2026-03-06 12:10:09 +08:00
[HttpGet("page")]
public async Task<PageResult<AppUserOutput>> Page([FromQuery] AppUserPageInput input)
{
var query = _appUserRep.AsQueryable()
.WhereIF(!string.IsNullOrWhiteSpace(input.Name), u => u.Name.Contains(input.Name))
.WhereIF(input.Gender.HasValue, u => u.Gender == input.Gender)
.WhereIF(!string.IsNullOrWhiteSpace(input.Mobile), u => u.Mobile.Contains(input.Mobile))
.WhereIF(input.AuditStatus.HasValue, u => u.AuditStatus == input.AuditStatus)
.WhereIF(input.StartTime.HasValue, u => u.CreatedTime >= input.StartTime)
.WhereIF(input.EndTime.HasValue, u => u.CreatedTime <= input.EndTime)
.OrderByDescending(u => u.CreatedTime);
return await query.Select<AppUserOutput>().ToPagedListAsync(input);
}
/// <summary>
/// 获取普通用户详情
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
2026-03-17 15:32:01 +08:00
[DisplayName("获取普通用户详情")]
2026-03-06 12:10:09 +08:00
[HttpGet("{id}")]
public async Task<AppUserOutput> Detail([FromRoute] long id)
{
return await _appUserRep.AsQueryable()
.Where(u => u.Id == id)
.Select<AppUserOutput>()
.FirstAsync();
}
/// <summary>
/// 审核普通用户
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
2026-03-17 15:32:01 +08:00
[DisplayName("审核普通用户")]
2026-03-06 12:10:09 +08:00
[HttpPost("audit")]
[UnitOfWork]
public async Task Audit([FromBody] AuditInput input)
{
if (input.Ids == null || !input.Ids.Any()) return;
// 更新状态
await _appUserRep.UpdateAsync(u => new AppUserEntity
{
AuditStatus = input.Status,
AuditRemark = input.Remark,
UpdatedTime = DateTime.Now,
UpdatedUserId = _authManager.UserId
}, u => input.Ids.Contains(u.Id));
}
2026-03-11 16:00:09 +08:00
#region 统一成员管理 (领队/教练/运动员)
2026-03-11 16:00:09 +08:00
/// <summary>
/// 统一分页查询成员
2026-03-11 16:00:09 +08:00
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpGet("member/page")]
[DisplayName("统一分页查询成员")]
public async Task<PageResult<MemberOutput>> MemberPage([FromQuery] MemberQueryInput input)
2026-03-11 16:00:09 +08:00
{
2026-03-30 17:06:12 +08:00
var (userId, merchantCertificationId) = await GetCurrentUserAndMerchantCertificationId();
2026-03-11 16:00:09 +08:00
var query = _appUserRep.AsQueryable()
.Where(x => x.PersonType == input.PersonType && !x.DeleteMark)
2026-03-11 16:00:09 +08:00
.WhereIF(!string.IsNullOrWhiteSpace(input.Name), x => x.Name.Contains(input.Name))
.WhereIF(input.Gender.HasValue, x => x.Gender == input.Gender)
2026-03-25 09:09:45 +08:00
.WhereIF(!string.IsNullOrWhiteSpace(input.Mobile), x => x.Mobile.Contains(input.Mobile))
.WhereIF(!string.IsNullOrWhiteSpace(input.IdCardNumber), u => u.IdCardNumber.Contains(input.IdCardNumber))
2026-03-20 02:00:34 +08:00
.WhereIF(input.PersonType == PersonType.Coach && !string.IsNullOrWhiteSpace(input.SkillLevel), x => x.SkillLevel == input.SkillLevel)
.WhereIF(input.PersonType == PersonType.Coach && input.CertificationStatus.HasValue, x => x.CertificationStatus == input.CertificationStatus)
.WhereIF(input.StartTime.HasValue, u => u.CreatedTime >= input.StartTime)
.WhereIF(input.EndTime.HasValue, u => u.CreatedTime <= input.EndTime)
2026-03-11 16:00:09 +08:00
.OrderByDescending(x => x.CreatedTime);
// 权限过滤 (管理员可看全部,这里简化的商户隔离逻辑)
2026-03-30 17:06:12 +08:00
if (merchantCertificationId.HasValue)
2026-03-11 16:00:09 +08:00
{
2026-03-30 17:06:12 +08:00
query = query.Where(x => x.MerchantCertificationId == merchantCertificationId.Value);
2026-03-11 16:00:09 +08:00
}
else
{
// 这里根据实际业务,如果普通用户只能看自己创建的,需要这个过滤
2026-03-30 17:06:12 +08:00
query = query.Where(x => x.CreatedUserId == userId && x.MerchantCertificationId == null);
2026-03-11 16:00:09 +08:00
}
var list = await query.ToPagedListAsync(input);
2026-04-13 02:09:59 +08:00
var result = list.Adapt<PageResult<MemberOutput>>();
// 设置Age字段:优先使用数据库中的Age,如果没有则根据BirthDate计算
foreach (var item in result.Rows)
{
if (item.Age.HasValue)
{
// 数据库中已有Age,直接使用
continue;
}
// 数据库中没有Age,根据BirthDate动态计算
if (item.BirthDate.HasValue)
{
item.Age = MemberOutput.CalculateAgeFromBirthDate(item.BirthDate);
}
}
return result;
2026-03-11 16:00:09 +08:00
}
/// <summary>
/// 统一新增/编辑成员
2026-03-11 16:00:09 +08:00
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("member/save")]
[DisplayName("统一新增/编辑成员")]
public async Task SaveUnifiedMember(CreateOrUpdateMemberInput input)
2026-03-11 16:00:09 +08:00
{
// 仅在新增运动员时验证验证码
if (!input.Id.HasValue && input.PersonType == PersonType.Athlete)
{
if (string.IsNullOrWhiteSpace(input.VerificationCode))
{
throw Oops.Bah("验证码不能为空");
}
var cacheKey = $"sms:code:{input.Mobile}";
var cachedCodeResult = _cachingProvider.Get<string>(cacheKey);
string? cachedCode = cachedCodeResult.Value;
if (!cachedCodeResult.HasValue || cachedCode != input.VerificationCode)
{
throw Oops.Bah("验证码错误或已过期");
}
}
2026-03-30 17:06:12 +08:00
var (userId, merchantCertificationId) = await GetCurrentUserAndMerchantCertificationId();
2026-03-11 16:00:09 +08:00
2026-04-13 13:23:00 +08:00
// 基于手机号的全局唯一性校验(不区分PersonType和权限)
var existingByPhone = await _appUserRep.AsQueryable()
.Where(x => x.Mobile == input.Mobile && !x.DeleteMark)
.FirstAsync();
2026-03-11 16:00:09 +08:00
AppUserEntity entity;
2026-04-13 13:23:00 +08:00
bool isNewRecord = false;
2026-03-11 16:00:09 +08:00
if (input.Id.HasValue)
{
2026-04-13 13:23:00 +08:00
// 编辑模式
2026-03-11 16:00:09 +08:00
entity = await _appUserRep.GetByIdAsync(input.Id.Value);
if (entity == null || entity.DeleteMark) throw Oops.Bah("成员不存在");
await CheckPermission(entity);
2026-04-13 13:23:00 +08:00
// 检查手机号是否被其他记录占用
if (existingByPhone != null && existingByPhone.Id != input.Id.Value)
{
throw Oops.Bah($"该手机号已被其他成员使用");
}
entity.UpdatedUserId = _authManager.UserId;
entity.UpdatedTime = DateTime.Now;
}
else if (existingByPhone != null)
{
// 手机号已存在,更新现有记录
entity = existingByPhone;
// 检查权限
await CheckPermission(entity);
2026-03-11 16:00:09 +08:00
entity.UpdatedUserId = _authManager.UserId;
entity.UpdatedTime = DateTime.Now;
}
else
{
2026-04-13 13:23:00 +08:00
// 手机号不存在,创建新记录
isNewRecord = true;
2026-03-11 16:00:09 +08:00
entity = new AppUserEntity
{
Id = YitIdHelper.NextId(),
PersonType = input.PersonType,
2026-03-30 17:06:12 +08:00
MerchantCertificationId = merchantCertificationId,
2026-03-11 16:00:09 +08:00
CreatedUserId = userId,
CreatedTime = DateTime.Now,
Status = AvailabilityStatus.Enable,
2026-04-13 13:37:50 +08:00
AuditStatus = AuditStatus.PendingSubmission
2026-03-11 16:00:09 +08:00
};
}
2026-04-13 13:23:00 +08:00
// 更新用户信息字段
2026-03-11 16:00:09 +08:00
entity.Name = input.Name;
entity.Gender = input.Gender;
2026-03-25 09:09:45 +08:00
entity.IdCardNumber = input.IdCardNumber;
2026-04-13 13:23:00 +08:00
entity.IdCardImageUrl = input.IdCardImageUrl;
entity.PassportPhotoUrl = input.PassportPhotoUrl;
2026-03-25 09:09:45 +08:00
entity.Mobile = input.Mobile;
2026-03-11 16:00:09 +08:00
entity.Remark = input.Remark;
2026-03-25 09:09:45 +08:00
entity.AuditRemark = "";
2026-04-13 02:09:59 +08:00
// 保存出生日期
2026-04-10 14:29:41 +08:00
entity.BirthDate = input.BirthDate;
2026-03-11 16:00:09 +08:00
2026-04-13 02:09:59 +08:00
// 保存年龄字段:如果用户填写了年龄,直接保存;如果没有填写但有出生日期,可以在查询时动态计算
entity.Age = input.Age;
2026-04-13 13:23:00 +08:00
// 更新运动员类型(如果是新记录或需要更新)
if (isNewRecord || entity.PersonType != input.PersonType)
{
entity.PersonType = input.PersonType;
}
// 更新认证单位信息
if (isNewRecord || entity.MerchantCertificationId != merchantCertificationId)
{
entity.MerchantCertificationId = merchantCertificationId;
}
2026-03-20 02:00:34 +08:00
if (input.PersonType == PersonType.Coach)
2026-03-11 16:00:09 +08:00
{
entity.SkillLevel = input.SkillLevel;
entity.CertificationStatus = input.CertificationStatus;
}
await _appUserRep.InsertOrUpdateAsync(entity);
}
#endregion
#region 领队/教练管理 (向下兼容)
2026-03-30 17:06:12 +08:00
private async Task<(long userId, long? merchantCertificationId)> GetCurrentUserAndMerchantCertificationId()
2026-03-11 16:00:09 +08:00
{
var userId = _authManager.UserId;
2026-03-30 17:06:12 +08:00
// 直接查询单位认证表,检查是否有审核通过的认证记录
var certification = await _db.Queryable<MerchantCertification>()
2026-04-13 03:44:49 +08:00
.FirstAsync(c => c.UserId == userId && !c.DeleteMark);
2026-03-30 17:06:12 +08:00
// 有审核通过的认证就返回认证ID,否则返回null
return (userId, certification?.Id);
2026-03-11 16:00:09 +08:00
}
private async Task CheckPermission(AppUserEntity entity)
2026-03-11 16:00:09 +08:00
{
2026-03-30 17:06:12 +08:00
var (userId, merchantCertificationId) = await GetCurrentUserAndMerchantCertificationId();
2026-03-30 17:06:12 +08:00
if (merchantCertificationId.HasValue)
{
2026-03-30 17:06:12 +08:00
if (entity.MerchantCertificationId != merchantCertificationId.Value) throw Oops.Bah("无权操作该成员");
}
else
{
2026-03-30 17:06:12 +08:00
if (entity.CreatedUserId != userId || entity.MerchantCertificationId != null) throw Oops.Bah("无权操作该成员");
}
2026-03-11 16:00:09 +08:00
}
/// <summary>
/// 删除成员
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
2026-03-17 15:32:01 +08:00
[DisplayName("删除成员")]
2026-03-11 16:00:09 +08:00
[HttpPost("member/delete")]
public async Task DeleteMember([FromBody] long id)
{
var entity = await _appUserRep.GetByIdAsync(id);
if (entity == null || entity.DeleteMark) throw Oops.Bah("成员不存在");
await CheckPermission(entity);
entity.DeleteMark = true;
entity.UpdatedUserId = _authManager.UserId;
entity.UpdatedTime = DateTime.Now;
await _appUserRep.UpdateAsync(entity);
}
/// <summary>
/// 批量删除成员
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
[HttpPost("member/batchDelete")]
[DisplayName("批量删除成员")]
public async Task BatchDeleteMember([FromBody] List<long> ids)
{
if (ids == null || !ids.Any()) throw Oops.Bah("请选择要删除的记录");
// 逻辑删除
await _appUserRep.UpdateAsync(x => new AppUserEntity { DeleteMark = true }, x => ids.Contains(x.Id));
}
2026-03-11 16:00:09 +08:00
/// <summary>
/// 变更成员状态
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
2026-03-17 15:32:01 +08:00
[DisplayName("变更成员状态")]
2026-03-11 16:00:09 +08:00
[HttpPost("member/status")]
public async Task ChangeMemberStatus(ChangeMemberStatusInput input)
{
var entity = await _appUserRep.GetByIdAsync(input.Id);
if (entity == null || entity.DeleteMark) throw Oops.Bah("成员不存在");
await CheckPermission(entity);
entity.Status = input.Status;
entity.UpdatedUserId = _authManager.UserId;
entity.UpdatedTime = DateTime.Now;
await _appUserRep.UpdateAsync(entity);
}
2026-03-12 10:30:13 +08:00
#endregion
2026-03-06 12:10:09 +08:00
}