366 lines
13 KiB
C#
366 lines
13 KiB
C#
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.Infrastructure.Aliyun.Ocr;
|
||
using YD_Event.Core.Enum;
|
||
using Mapster;
|
||
|
||
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 OcrService _ocrService;
|
||
|
||
public AppUserService(ISqlSugarRepository<AppUserEntity> appUserRep,
|
||
ISqlSugarClient db,
|
||
AuthManager authManager,
|
||
OcrService ocrService) : base(appUserRep)
|
||
{
|
||
_appUserRep = appUserRep;
|
||
_db = db;
|
||
_authManager = authManager;
|
||
_ocrService = ocrService;
|
||
}
|
||
|
||
/// <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.User) // 仅查询普通用户
|
||
.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>
|
||
[DisplayName("获取普通用户详情")]
|
||
[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>
|
||
[DisplayName("审核普通用户")]
|
||
[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));
|
||
}
|
||
|
||
#region 统一成员管理 (领队/教练/运动员)
|
||
|
||
/// <summary>
|
||
/// 统一分页查询成员
|
||
/// </summary>
|
||
/// <param name="input"></param>
|
||
/// <returns></returns>
|
||
[HttpGet("member/page")]
|
||
[DisplayName("统一分页查询成员")]
|
||
public async Task<PageResult<MemberOutput>> MemberPage([FromQuery] MemberQueryInput input)
|
||
{
|
||
var (userId, merchantId) = await GetCurrentUserAndMerchantId();
|
||
|
||
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 (merchantId.HasValue)
|
||
{
|
||
query = query.Where(x => x.MerchantId == merchantId.Value);
|
||
}
|
||
else
|
||
{
|
||
// 这里根据实际业务,如果普通用户只能看自己创建的,需要这个过滤
|
||
query = query.Where(x => x.CreatedUserId == userId && x.MerchantId == null);
|
||
}
|
||
|
||
var list = await query.ToPagedListAsync(input);
|
||
return list.Adapt<PageResult<MemberOutput>>();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 统一新增/编辑成员
|
||
/// </summary>
|
||
/// <param name="input"></param>
|
||
/// <returns></returns>
|
||
[HttpPost("member/save")]
|
||
[DisplayName("统一新增/编辑成员")]
|
||
public async Task SaveUnifiedMember(CreateOrUpdateMemberInput input)
|
||
{
|
||
var (userId, merchantId) = await GetCurrentUserAndMerchantId();
|
||
|
||
// 校验唯一性 (手机号或身份证号,具体根据角色定)
|
||
// 运动员主要查身份证,领队教练查手机号。这里综合校验。
|
||
var queryPhone = _appUserRep.AsQueryable()
|
||
.Where(x => x.Mobile == input.Mobile && x.PersonType == input.PersonType && !x.DeleteMark);
|
||
|
||
var queryIdCard = _appUserRep.AsQueryable()
|
||
.Where(x => x.IdCardNumber == input.IdCardNumber && x.PersonType == input.PersonType && !x.DeleteMark);
|
||
|
||
if (input.Id.HasValue)
|
||
{
|
||
queryPhone = queryPhone.Where(x => x.Id != input.Id.Value);
|
||
queryIdCard = queryIdCard.Where(x => x.Id != input.Id.Value);
|
||
}
|
||
|
||
if (merchantId.HasValue)
|
||
{
|
||
queryPhone = queryPhone.Where(x => x.MerchantId == merchantId.Value);
|
||
queryIdCard = queryIdCard.Where(x => x.MerchantId == merchantId.Value);
|
||
}
|
||
else
|
||
{
|
||
queryPhone = queryPhone.Where(x => x.CreatedUserId == userId && x.MerchantId == null);
|
||
queryIdCard = queryIdCard.Where(x => x.CreatedUserId == userId && x.MerchantId == null);
|
||
}
|
||
|
||
// 只有领队和教练强校验手机号
|
||
if (input.PersonType == PersonType.Leader || input.PersonType == PersonType.Coach)
|
||
{
|
||
if (await queryPhone.AnyAsync()) throw Oops.Bah($"该手机号的成员已存在");
|
||
}
|
||
// 运动员强校验身份证
|
||
if (input.PersonType == PersonType.Athlete)
|
||
{
|
||
if (await queryIdCard.AnyAsync()) throw Oops.Bah($"该身份证号的成员已存在");
|
||
}
|
||
|
||
AppUserEntity entity;
|
||
if (input.Id.HasValue)
|
||
{
|
||
entity = await _appUserRep.GetByIdAsync(input.Id.Value);
|
||
if (entity == null || entity.DeleteMark) throw Oops.Bah("成员不存在");
|
||
await CheckPermission(entity);
|
||
|
||
entity.UpdatedUserId = _authManager.UserId;
|
||
entity.UpdatedTime = DateTime.Now;
|
||
}
|
||
else
|
||
{
|
||
entity = new AppUserEntity
|
||
{
|
||
Id = YitIdHelper.NextId(),
|
||
PersonType = input.PersonType,
|
||
MerchantId = merchantId,
|
||
CreatedUserId = userId,
|
||
CreatedTime = DateTime.Now,
|
||
Status = AvailabilityStatus.Enable,
|
||
AuditStatus = AuditStatus.Pass
|
||
};
|
||
}
|
||
|
||
entity.Name = input.Name;
|
||
entity.Gender = input.Gender;
|
||
entity.IdCardNumber = input.IdCardNumber;
|
||
entity.IdCardImageUrl = input.IdCardUrl;
|
||
entity.Mobile = input.Mobile;
|
||
entity.Remark = input.Remark;
|
||
entity.AuditRemark = "";
|
||
|
||
// 计算年龄逻辑
|
||
if (input.BirthDate.HasValue)
|
||
{
|
||
entity.BirthDate = input.BirthDate;
|
||
var today = DateTime.Today;
|
||
var age = today.Year - input.BirthDate.Value.Year;
|
||
if (input.BirthDate.Value.Date > today.AddYears(-age)) age--;
|
||
entity.Age = age;
|
||
}
|
||
else
|
||
{
|
||
entity.Age = input.Age;
|
||
}
|
||
|
||
if (input.PersonType == PersonType.Coach)
|
||
{
|
||
entity.SkillLevel = input.SkillLevel;
|
||
entity.CertificationStatus = input.CertificationStatus;
|
||
}
|
||
|
||
await _appUserRep.InsertOrUpdateAsync(entity);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 领队/教练管理 (向下兼容)
|
||
|
||
private async Task<(long userId, long? merchantId)> GetCurrentUserAndMerchantId()
|
||
{
|
||
var userId = _authManager.UserId;
|
||
var merchant = await _db.Queryable<YD_Event.Core.Entities.Merchant>()
|
||
.FirstAsync(m => m.CreatedUserId == userId && !m.DeleteMark);
|
||
return (userId, merchant?.Id);
|
||
}
|
||
|
||
private async Task CheckPermission(AppUserEntity entity)
|
||
{
|
||
var (userId, merchantId) = await GetCurrentUserAndMerchantId();
|
||
|
||
if (merchantId.HasValue)
|
||
{
|
||
if (entity.MerchantId != merchantId.Value) throw Oops.Bah("无权操作该成员");
|
||
}
|
||
else
|
||
{
|
||
if (entity.CreatedUserId != userId || entity.MerchantId != null) throw Oops.Bah("无权操作该成员");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除成员
|
||
/// </summary>
|
||
/// <param name="id"></param>
|
||
/// <returns></returns>
|
||
[DisplayName("删除成员")]
|
||
[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));
|
||
}
|
||
|
||
/// <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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 识别身份证信息
|
||
/// </summary>
|
||
/// <param name="input"></param>
|
||
/// <returns></returns>
|
||
[HttpPost("member/recognize/idcard")]
|
||
[DisplayName("OCR识别身份证")]
|
||
public async Task<IdCardRecognitionResult> RecognizeIdCard([FromBody] AthleteOcrInput input)
|
||
{
|
||
// 输入验证
|
||
if (string.IsNullOrWhiteSpace(input.ImageUrl))
|
||
{
|
||
throw Oops.Bah("护照图片URL不能为空");
|
||
}
|
||
|
||
// URL格式验证(简单的格式检查)
|
||
if (!input.ImageUrl.StartsWith("http://") && !input.ImageUrl.StartsWith("https://"))
|
||
{
|
||
throw Oops.Bah("请输入有效的图片URL地址");
|
||
}
|
||
|
||
return await _ocrService.RecognizeIdCardAsync(input.ImageUrl);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 识别护照信息
|
||
/// </summary>
|
||
/// <param name="input">护照图片URL输入</param>
|
||
/// <returns>护照识别结果</returns>
|
||
[HttpPost("member/recognize/passport")]
|
||
[DisplayName("OCR识别护照")]
|
||
public async Task<PassportRecognitionResult> RecognizePassport([FromBody] AthleteOcrInput input)
|
||
{
|
||
// 输入验证
|
||
if (string.IsNullOrWhiteSpace(input.ImageUrl))
|
||
{
|
||
throw Oops.Bah("护照图片URL不能为空");
|
||
}
|
||
|
||
// URL格式验证(简单的格式检查)
|
||
if (!input.ImageUrl.StartsWith("http://") && !input.ImageUrl.StartsWith("https://"))
|
||
{
|
||
throw Oops.Bah("请输入有效的图片URL地址");
|
||
}
|
||
|
||
return await _ocrService.RecognizePassportAsync(input.ImageUrl);
|
||
}
|
||
|
||
#endregion
|
||
}
|