马梓彭 99a1857088 完善二期接口
补齐接口文档和上线SQL
优化底层保存逻辑
2026-08-30 21:46:05 +08:00

726 lines
27 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 YD_Event.Application.AppUser.Dtos;
using YD_Event.Application.Auth;
using YD_Event.Core.Entities;
using AppUserEntity = YD_Event.Core.Entities.AppUser;
using YD_Event.Core.Enum;
using Mapster;
using EasyCaching.Core;
namespace YD_Event.Application.AppUser;
/// <summary>
/// 普通用户服务
/// </summary>
public class AppUserService : BaseService<AppUserEntity>, ITransient
{
private readonly ISqlSugarRepository<AppUserEntity> _appUserRep;
private readonly ISqlSugarClient _db;
private readonly AuthManager _authManager;
private readonly IEasyCachingProvider _cachingProvider;
public AppUserService(ISqlSugarRepository<AppUserEntity> appUserRep,
ISqlSugarClient db,
AuthManager authManager,
IEasyCachingProvider cachingProvider) : base(appUserRep)
{
_appUserRep = appUserRep;
_db = db;
_authManager = authManager;
_cachingProvider = cachingProvider;
}
/// <summary>
/// 分页查询普通用户
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("分页查询普通用户")]
[HttpGet("page")]
public async Task<PageResult<AppUserOutput>> Page([FromQuery] AppUserPageInput input)
{
var query = _appUserRep.AsQueryable()
.Where(u => u.PersonType == PersonType.Athlete)
// 仅显示真正在小程序端自主注册/登录过的用户:有密码、或有微信昵称/头像
// 单位后台添加后被移除的运动员(无密码、无微信信息)不应出现在此列表
.Where(u => u.Password != null || u.NickName != null || u.Avatar != null)
.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);
var result = await query.Select<AppUserOutput>().ToPagedListAsync(input);
// 根据认证类型填充统一的 CertImageUrl 和 CertNumber 字段
foreach (var item in result.Rows)
{
FillCertFields(item);
}
return result;
}
/// <summary>
/// 根据认证类型填充统一的认证证件字段(CertImageUrl, CertNumber)
/// </summary>
private static void FillCertFields(AppUserOutput output)
{
output.CertImageUrl = output.AuthType switch
{
1 => output.IdCardImageUrl,
2 => output.PassportPhotoUrl,
3 => output.HouseholdPhotoUrl,
_ => output.IdCardImageUrl ?? output.HouseholdPhotoUrl ?? output.PassportPhotoUrl
};
output.CertNumber = output.AuthType switch
{
1 or 3 => output.IdCardNumber,
2 => output.PassportNumber,
_ => output.IdCardNumber ?? output.PassportNumber
};
}
/// <summary>
/// 获取普通用户详情
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[DisplayName("获取普通用户详情")]
[HttpGet("{id}")]
public async Task<AppUserOutput> Detail([FromRoute] long id)
{
var output = await _appUserRep.AsQueryable()
.Where(u => u.Id == id)
.Select<AppUserOutput>()
.FirstAsync();
// 根据认证类型填充统一的 CertImageUrl 和 CertNumber 字段
FillCertFields(output);
return output;
}
/// <summary>
/// 审核普通用户
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("审核普通用户")]
[HttpPost("audit")]
[UnitOfWork]
public async Task Audit([FromBody] AuditInput input)
{
if (input.Ids == null || !input.Ids.Any()) return;
if (input.Status != AuditStatus.Pass && input.Status != AuditStatus.Reject)
throw Oops.Bah("审核状态不正确");
// 检查是否存在未提交认证申请的用户
var pendingUsers = await _appUserRep.AsQueryable()
.Where(u => input.Ids.Contains(u.Id) && u.AuditStatus == AuditStatus.PendingSubmission)
.Select(u => u.Name)
.ToListAsync();
if (pendingUsers.Any())
{
throw Oops.Bah("所选人员中存在未提交认证的申请,请核对后再操作");
}
var now = DateTime.Now;
var auditUserId = _authManager.UserId;
// 审核人与更新时间分别留痕,后续普通编辑不会覆盖审核记录。
await _appUserRep.UpdateAsync(u => new AppUserEntity
{
AuditStatus = input.Status,
AuditRemark = input.Remark,
AuditUserId = auditUserId,
AuditTime = now,
UpdatedTime = now,
UpdatedUserId = auditUserId
}, u => input.Ids.Contains(u.Id));
}
#region 统一成员管理 (领队/教练/运动员)
/// <summary>
/// 统一分页查询成员
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpGet("member/page")]
[DisplayName("统一分页查询成员")]
public async Task<PageResult<MemberOutput>> MemberPage([FromQuery] MemberQueryInput input)
{
var (userId, merchantCertificationId) = await GetCurrentUserAndMerchantCertificationId();
var query = _appUserRep.AsQueryable()
.Where(x => x.PersonType == input.PersonType && !x.DeleteMark)
.WhereIF(!string.IsNullOrWhiteSpace(input.Name), x => x.Name.Contains(input.Name))
.WhereIF(input.Gender.HasValue, x => x.Gender == input.Gender)
.WhereIF(!string.IsNullOrWhiteSpace(input.Mobile), x => x.Mobile.Contains(input.Mobile))
.WhereIF(!string.IsNullOrWhiteSpace(input.IdCardNumber), u => u.IdCardNumber.Contains(input.IdCardNumber))
.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)
.OrderByDescending(x => x.CreatedTime);
// 权限过滤 (管理员可看全部,这里简化的商户隔离逻辑)
if (merchantCertificationId.HasValue)
{
query = query.Where(x => x.MerchantCertificationId == merchantCertificationId.Value);
}
else
{
// 这里根据实际业务,如果普通用户只能看自己创建的,需要这个过滤
query = query.Where(x => x.CreatedUserId == userId && x.MerchantCertificationId == null);
}
var list = await query.ToPagedListAsync(input);
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;
}
/// <summary>
/// 统一新增/编辑成员
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("member/save")]
[DisplayName("统一新增/编辑成员")]
[UnitOfWork]
public async Task SaveUnifiedMember(CreateOrUpdateMemberInput input)
{
var (userId, merchantCertificationId) = await GetCurrentUserAndMerchantCertificationId();
// 检查是否有跨单位操作权限
if (!merchantCertificationId.HasValue)
{
throw Oops.Bah("当前用户未关联单位,无法进行操作");
}
// 根据认证类型决定查重策略:
// - 运动员 + 有认证类型 + 有证件号 → 用证件号查重
// - 其他情况 → 用手机号查重(原逻辑)
var mobileIsEmpty = string.IsNullOrWhiteSpace(input.Mobile);
bool useCertCheck = input.PersonType == PersonType.Athlete && input.AuthType > 0 && (
((input.AuthType == 1 || input.AuthType == 3) && !string.IsNullOrWhiteSpace(input.IdCardNumber))
|| (input.AuthType == 2 && !string.IsNullOrWhiteSpace(input.PassportNumber))
);
// 查重基础查询(排除自身ID)
ISugarQueryable<AppUserEntity> BuildBaseQuery()
{
var q = _appUserRep.AsQueryable()
.Where(x => x.PersonType == input.PersonType && !x.DeleteMark);
if (input.Id.HasValue)
{
q = q.Where(x => x.Id != input.Id.Value);
}
return q;
}
// ========== 查重流程(证件号或手机号,流程一致) ==========
AppUserEntity? existingInOtherUnit = null;
AppUserEntity? existingWithoutUnit = null;
if (useCertCheck)
{
var certQuery = BuildBaseQuery();
// 按认证类型明确匹配不同证件字段
if (input.AuthType == 1)
{
// 身份证认证 → 用 IdCardNumber 查重
certQuery = certQuery.Where(x => x.IdCardNumber == input.IdCardNumber);
}
else if (input.AuthType == 2)
{
// 护照认证 → 用 PassportNumber 查重
certQuery = certQuery.Where(x => x.PassportNumber == input.PassportNumber);
}
else if (input.AuthType == 3)
{
// 户口本认证 → 用 IdCardNumber 查重
certQuery = certQuery.Where(x => x.IdCardNumber == input.IdCardNumber);
}
var certMatches = await certQuery.ToListAsync();
// 本单位同证件号 → 提示重复
var duplicateInSameUnit = certMatches.FirstOrDefault(x => x.MerchantCertificationId == merchantCertificationId);
if (duplicateInSameUnit != null)
{
throw Oops.Bah($"该{GetCertDisplay(input.AuthType)}在本单位已存在相同类型的成员,请核对信息");
}
// 其他单位有记录 → 单位更换
existingInOtherUnit = certMatches.FirstOrDefault(x => x.MerchantCertificationId != null && x.MerchantCertificationId != merchantCertificationId);
// 未加入单位的记录 → 复用
if (existingInOtherUnit == null)
{
existingWithoutUnit = certMatches.FirstOrDefault(x => x.MerchantCertificationId == null);
}
}
else
{
// 原逻辑:按手机号查重
if (!mobileIsEmpty)
{
existingInOtherUnit = await _appUserRep.AsQueryable()
.Where(x => x.Mobile == input.Mobile
&& x.PersonType == input.PersonType
&& x.MerchantCertificationId != merchantCertificationId
&& x.MerchantCertificationId != null
&& !x.DeleteMark)
.FirstAsync();
}
}
AppUserEntity? entity = null;
bool isUnitTransfer = false;
bool isNewRecord = false;
long? originalMerchantId = null;
if (existingInOtherUnit != null)
{
// 单位更换流程
isUnitTransfer = true;
entity = existingInOtherUnit;
originalMerchantId = entity.MerchantCertificationId;
}
else if (existingWithoutUnit != null)
{
// 找到已存在但未加入任何单位的记录,复用该记录(证件号查重时进入)
entity = existingWithoutUnit;
entity.UpdatedUserId = _authManager.UserId;
entity.UpdatedTime = DateTime.Now;
}
else
{
// 按原手机号继续查询(仅当未使用证件号查重时)
if (!useCertCheck && !mobileIsEmpty)
{
var mobileMatch = await _appUserRep.AsQueryable()
.Where(x => x.Mobile == input.Mobile
&& x.PersonType == input.PersonType
&& x.MerchantCertificationId == null
&& !x.DeleteMark)
.FirstAsync();
if (mobileMatch != null)
{
entity = mobileMatch;
entity.UpdatedUserId = _authManager.UserId;
entity.UpdatedTime = DateTime.Now;
}
else
{
isNewRecord = true;
}
}
else
{
// 没有找到任何记录,标记为新增
isNewRecord = true;
}
}
if (input.Id.HasValue)
{
// 编辑模式
entity = await _appUserRep.GetByIdAsync(input.Id.Value);
if (entity == null || entity.DeleteMark) throw Oops.Bah("成员不存在");
// 手机号为空时,不进行手机号重复检测
if (!mobileIsEmpty)
{
// 检查手机号是否被本单位内同类型的其他记录占用
var duplicateInSameUnit = await _appUserRep.AsQueryable()
.Where(x => x.Mobile == input.Mobile
&& x.PersonType == input.PersonType
&& x.MerchantCertificationId == merchantCertificationId
&& !x.DeleteMark
&& x.Id != input.Id.Value)
.FirstAsync();
if (duplicateInSameUnit != null)
{
throw Oops.Bah($"该手机号在本单位已存在相同类型的成员");
}
}
entity.UpdatedUserId = _authManager.UserId;
entity.UpdatedTime = DateTime.Now;
}
else if (isNewRecord)
{
// 新增模式:手机号不为空时,检查本单位内是否有相同手机号且相同类型的记录
if (!mobileIsEmpty)
{
var existingInSameUnit = await _appUserRep.AsQueryable()
.Where(x => x.Mobile == input.Mobile
&& x.PersonType == input.PersonType
&& x.MerchantCertificationId == merchantCertificationId
&& !x.DeleteMark)
.FirstAsync();
if (existingInSameUnit != null)
{
// 本单位内已有相同手机号且相同类型的记录,不允许重复添加
throw Oops.Bah($"该手机号在本单位已存在相同类型的成员");
}
}
// 新增记录
entity = new AppUserEntity
{
Id = YitIdHelper.NextId(),
PersonType = input.PersonType,
MerchantCertificationId = merchantCertificationId,
CreatedUserId = userId,
CreatedTime = DateTime.Now,
Status = AvailabilityStatus.Enable,
AuditStatus = AuditStatus.PendingSubmission
};
// 单位添加运动员时,验证码改为非必填;仅在手机号+验证码均存在时才校验
if (input.PersonType == PersonType.Athlete && !mobileIsEmpty && !string.IsNullOrWhiteSpace(input.VerificationCode))
{
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("验证码错误或已过期");
}
}
if (isUnitTransfer)
{
await _appUserRep.SoftDeleteByIdAsync(existingInOtherUnit.Id);
}
}
// 更新用户信息字段
entity.Name = input.Name;
entity.Gender = input.Gender;
entity.IdCardNumber = input.IdCardNumber;
entity.Mobile = input.Mobile;
entity.Remark = input.Remark;
entity.BirthDate = input.BirthDate;
entity.Age = input.Age;
// 单位更换后原审核结论不再适用于新归属,重置为待提交;普通编辑保留审核历史。
if (isUnitTransfer)
{
entity.AuditStatus = AuditStatus.PendingSubmission;
entity.AuditRemark = null;
entity.AuditUserId = null;
entity.AuditTime = null;
}
// 根据认证类型把图片URL和证件号写入对应字段,并清空其他类型图片
if (input.AuthType == 1)
{
entity.AuthType = 1;
entity.IdCardImageUrl = input.IdCardImageUrl;
entity.IdCardBackImageUrl = input.IdCardBackImageUrl;
entity.PassportPhotoUrl = null;
entity.PassportNumber = null;
entity.HouseholdPhotoUrl = null;
}
else if (input.AuthType == 2)
{
entity.AuthType = 2;
entity.PassportPhotoUrl = input.PassportPhotoUrl ?? input.IdCardImageUrl;
entity.PassportNumber = input.PassportNumber;
entity.IdCardImageUrl = null;
entity.IdCardBackImageUrl = null;
entity.IdCardNumber = null;
entity.HouseholdPhotoUrl = null;
}
else if (input.AuthType == 3)
{
entity.AuthType = 3;
entity.HouseholdPhotoUrl = input.HouseholdPhotoUrl ?? input.IdCardImageUrl;
entity.IdCardImageUrl = null;
entity.IdCardBackImageUrl = null;
entity.PassportPhotoUrl = null;
entity.PassportNumber = null;
}
else
{
// AuthType == 0 或其他值:未认证,不修改认证相关字段(保持原样)
// 但清空可能的残留图片URL,避免脏数据
entity.AuthType = 0;
entity.IdCardImageUrl = null;
entity.IdCardBackImageUrl = null;
entity.PassportPhotoUrl = null;
entity.PassportNumber = null;
entity.HouseholdPhotoUrl = null;
}
// 更新认证单位信息
var oldMerchantId = entity.MerchantCertificationId;
if (entity.MerchantCertificationId != merchantCertificationId)
{
entity.MerchantCertificationId = merchantCertificationId;
entity.UpdatedUserId = _authManager.UserId;
entity.UpdatedTime = DateTime.Now;
}
if (input.PersonType == PersonType.Coach)
{
entity.SkillLevel = input.SkillLevel;
entity.CertificationStatus = input.CertificationStatus;
}
// 保存用户信息
await _appUserRep.InsertOrUpdateAsync(entity);
// 如果是单位更换,处理团队关系
if (isUnitTransfer && oldMerchantId.HasValue)
{
await HandleTeamTransfer(entity.Id, oldMerchantId.Value, merchantCertificationId.Value);
}
}
/// <summary>
/// 处理单位更换时的团队成员关系
/// </summary>
/// <param name="userId">用户ID</param>
/// <param name="oldMerchantId">原单位ID</param>
/// <param name="newMerchantId">新单位ID</param>
/// <returns></returns>
private async Task HandleTeamTransfer(long userId, long oldMerchantId, long newMerchantId)
{
// 从原单位的所有团队中移除该成员
var oldTeamIds = await _db.Queryable<YD_Event.Core.Entities.Team>()
.Where(t => t.MerchantCertificationId == oldMerchantId && !t.DeleteMark)
.Select(t => t.Id)
.ToListAsync();
if (oldTeamIds.Any())
{
await _db.Updateable<TeamMember>()
.SetColumns(tm => new TeamMember
{
DeleteMark = true,
UpdatedTime = DateTime.Now,
UpdatedUserId = _authManager.UserId
}, appendColumnsByDataFilter: true)
.Where(tm => tm.UserId == userId
&& oldTeamIds.Contains(tm.TeamId)
&& !tm.DeleteMark)
.ExecuteCommandAsync();
}
}
/// <summary>
/// 获取人员类型显示名称
/// </summary>
/// <param name="personType"></param>
/// <returns></returns>
private static string GetPersonTypeDisplay(PersonType personType)
{
return personType switch
{
PersonType.Athlete => "运动员",
PersonType.Coach => "教练",
PersonType.Leader => "领队",
_ => "成员"
};
}
/// <summary>
/// 获取证件类型显示名称
/// </summary>
/// <param name="authType"></param>
/// <returns></returns>
private static string GetCertDisplay(int authType)
{
return authType switch
{
1 => "身份证号",
2 => "护照号码",
3 => "身份证号",
_ => "证件号"
};
}
#endregion
#region 领队/教练管理 (向下兼容)
private async Task<(long userId, long? merchantCertificationId)> GetCurrentUserAndMerchantCertificationId()
{
var userId = _authManager.UserId;
// 直接查询单位认证表,检查是否有审核通过的认证记录
var certification = await _db.Queryable<MerchantCertification>()
.FirstAsync(c => c.UserId == userId && !c.DeleteMark);
// 有审核通过的认证就返回认证ID,否则返回null
return (userId, certification?.Id);
}
private async Task CheckPermission(AppUserEntity entity)
{
var (userId, merchantCertificationId) = await GetCurrentUserAndMerchantCertificationId();
if (merchantCertificationId.HasValue)
{
if (entity.MerchantCertificationId != merchantCertificationId.Value) throw Oops.Bah("无权操作该成员");
}
else
{
if (entity.CreatedUserId != userId || entity.MerchantCertificationId != null) throw Oops.Bah("无权操作该成员");
}
}
/// <summary>
/// 删除成员(解除与单位的关联,而非删除用户)
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[DisplayName("删除成员")]
[HttpPost("member/delete")]
[UnitOfWork]
public async Task DeleteMember([FromBody] long id)
{
var entity = await _appUserRep.GetByIdAsync(id);
if (entity == null || entity.DeleteMark) throw Oops.Bah("成员不存在");
await CheckPermission(entity);
// 1. 先保存原来的 MerchantCertificationId 用于后续处理
var originalMerchantId = entity.MerchantCertificationId;
// 2. 解除用户与单位的关联
entity.MerchantCertificationId = null;
entity.UpdatedUserId = _authManager.UserId;
entity.UpdatedTime = DateTime.Now;
await _appUserRep.UpdateAsync(entity);
// 3. 如果原来有关联单位,需要从该单位的所有团队中移除该成员
if (originalMerchantId.HasValue)
{
// 查找该单位的所有团队
var teamIds = await _db.Queryable<YD_Event.Core.Entities.Team>()
.Where(t => t.MerchantCertificationId == originalMerchantId.Value && !t.DeleteMark)
.Select(t => t.Id)
.ToListAsync();
if (teamIds.Any())
{
// 从所有相关团队中移除该成员
await _db.Updateable<TeamMember>()
.SetColumns(tm => new TeamMember
{
DeleteMark = true,
UpdatedTime = DateTime.Now,
UpdatedUserId = _authManager.UserId
}, appendColumnsByDataFilter: true)
.Where(tm => tm.UserId == id
&& teamIds.Contains(tm.TeamId)
&& !tm.DeleteMark)
.ExecuteCommandAsync();
}
}
}
/// <summary>
/// 批量删除成员(解除与单位的关联,而非删除用户)
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
[HttpPost("member/batchDelete")]
[DisplayName("批量删除成员")]
[UnitOfWork]
public async Task BatchDeleteMember([FromBody] List<long> ids)
{
if (ids == null || !ids.Any()) throw Oops.Bah("请选择要删除的记录");
// 获取当前用户的单位ID
var (_, merchantCertificationId) = await GetCurrentUserAndMerchantCertificationId();
// 1. 解除所有选中用户与当前单位的关联
await _appUserRep.UpdateAsync(x => new AppUserEntity
{
MerchantCertificationId = null,
UpdatedUserId = _authManager.UserId,
UpdatedTime = DateTime.Now
}, x => ids.Contains(x.Id));
// 2. 如果有单位ID,从该单位的所有团队中移除这些成员
if (merchantCertificationId.HasValue)
{
// 查找该单位的所有团队
var teamIds = await _db.Queryable<YD_Event.Core.Entities.Team>()
.Where(t => t.MerchantCertificationId == merchantCertificationId.Value && !t.DeleteMark)
.Select(t => t.Id)
.ToListAsync();
if (teamIds.Any())
{
// 从所有相关团队中移除这些成员
await _db.Updateable<TeamMember>()
.SetColumns(tm => new TeamMember
{
DeleteMark = true,
UpdatedTime = DateTime.Now,
UpdatedUserId = _authManager.UserId
}, appendColumnsByDataFilter: true)
.Where(tm => ids.Contains(tm.UserId)
&& teamIds.Contains(tm.TeamId)
&& !tm.DeleteMark)
.ExecuteCommandAsync();
}
}
}
/// <summary>
/// 变更成员状态
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("变更成员状态")]
[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);
}
#endregion
}