using Health.Application.Auth; using Health.Infrastructure.Services; namespace Health.Infrastructure.Auth; public sealed class AuthService( AppDbContext db, JwtProvider jwt, SmsService sms, AppleTokenValidator appleValidator // Apple Sign-In JWT 验证 ) : IAuthService { private const string AdminPhone = "12345678910"; private const string AdminSmsCode = "000000"; private static readonly Guid AdminId = Guid.Parse("00000000-0000-0000-0000-000000000001"); private readonly AppDbContext _db = db; private readonly JwtProvider _jwt = jwt; private readonly SmsService _sms = sms; private readonly AppleTokenValidator _appleValidator = appleValidator; public async Task SendCodeAsync(string phone, bool revealDevelopmentCode, CancellationToken ct) { var code = phone == AdminPhone ? AdminSmsCode : _sms.GenerateCode(); await _db.VerificationCodes.AddAsync(new VerificationCode { Id = Guid.NewGuid(), Phone = phone, Code = code, ExpiresAt = DateTime.UtcNow.AddMinutes(5), }, ct); await _db.SaveChangesAsync(ct); if (phone != AdminPhone) await _sms.SendCodeAsync(phone, code); return new AuthResult(0, new { success = true, devCode = revealDevelopmentCode ? code : null }); } public async Task RegisterAsync(RegisterCommand command, CancellationToken ct) { var code = await TakeCodeAsync(command.Phone, command.SmsCode, ct); if (code == null) return Error(40001, "验证码错误或已过期"); if (await _db.Users.AnyAsync(x => x.Phone == command.Phone, ct)) return Error(40002, "该手机号已注册,请直接登录"); if (string.IsNullOrWhiteSpace(command.Name)) return Error(40003, "请输入姓名"); if (!await _db.Doctors.AnyAsync(x => x.Id == command.DoctorId && x.IsActive, ct)) return Error(40004, "所选医生不存在或已停诊"); code.IsUsed = true; var user = new User { Id = Guid.NewGuid(), Phone = command.Phone, Role = "User", Name = command.Name, DoctorId = command.DoctorId, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow, }; _db.Users.Add(user); _db.NotificationPreferences.Add(new NotificationPreference { Id = Guid.NewGuid(), UserId = user.Id }); _db.HealthArchives.Add(new HealthArchive { Id = Guid.NewGuid(), UserId = user.Id }); var tokens = AddTokens(user.Id, user.Phone, user.Role); await _db.SaveChangesAsync(ct); return new AuthResult(0, new { tokens.accessToken, tokens.refreshToken, user = new { user.Id, user.Phone, user.Role, isNew = true } }); } public async Task LoginAsync(string phone, string smsCode, CancellationToken ct) { var code = await TakeCodeAsync(phone, smsCode, ct); if (code == null) return Error(40001, "验证码错误或已过期"); code.IsUsed = true; if (phone == AdminPhone) { var tokens = AddTokens(AdminId, AdminPhone, "Admin"); await _db.SaveChangesAsync(ct); return new AuthResult(0, new { tokens.accessToken, tokens.refreshToken, user = new { id = AdminId, phone = AdminPhone, role = "Admin", name = "管理员", isNew = false } }); } var user = await _db.Users.FirstOrDefaultAsync(x => x.Phone == phone, ct); if (user == null) return Error(40004, "该手机号未注册,请先注册"); user.UpdatedAt = DateTime.UtcNow; var userTokens = AddTokens(user.Id, user.Phone, user.Role); await _db.SaveChangesAsync(ct); return new AuthResult(0, new { userTokens.accessToken, userTokens.refreshToken, user = new { user.Id, user.Phone, user.Role, user.Name, user.Gender, user.AvatarUrl, BirthDate = user.BirthDate?.ToString("yyyy-MM-dd"), isNew = false } }); } public async Task RefreshAsync(string refreshToken, CancellationToken ct) { var oldToken = await _db.RefreshTokens.FirstOrDefaultAsync(x => x.Token == refreshToken && !x.IsRevoked, ct); if (oldToken == null || oldToken.ExpiresAt < DateTime.UtcNow) return Error(40002, "登录已过期,请重新登录"); oldToken.IsRevoked = true; if (oldToken.UserId == AdminId) { var tokens = AddTokens(AdminId, AdminPhone, "Admin"); await _db.SaveChangesAsync(ct); return new AuthResult(0, new { tokens.accessToken, tokens.refreshToken, user = new { id = AdminId, phone = AdminPhone, role = "Admin", name = "管理员" } }); } var user = await _db.Users.FindAsync([oldToken.UserId], ct); if (user == null) return Error(40002, "用户不存在"); var userTokens = AddTokens(user.Id, user.Phone, user.Role); await _db.SaveChangesAsync(ct); return new AuthResult(0, new { userTokens.accessToken, userTokens.refreshToken, user = new { user.Id, user.Phone, user.Role, user.Name, user.Gender, user.AvatarUrl, BirthDate = user.BirthDate?.ToString("yyyy-MM-dd") } }); } public async Task LogoutAsync(string refreshToken, CancellationToken ct) { var token = await _db.RefreshTokens.FirstOrDefaultAsync(x => x.Token == refreshToken, ct); if (token != null) token.IsRevoked = true; await _db.SaveChangesAsync(ct); } public async Task AppleLoginAsync(AppleLoginCommand command, CancellationToken ct) { // 1. 验证 Apple identityToken string appleUserId; try { appleUserId = await _appleValidator.ValidateAsync(command.IdentityToken, ct); } catch (OperationCanceledException) when (ct.IsCancellationRequested) { throw; } catch (Exception) { return Error(40005, "Apple 登录验证失败,请重试"); } // 2. 查找已有用户 var existingUser = await _db.Users.FirstOrDefaultAsync(x => x.AppleUserId == appleUserId, ct); if (existingUser != null) { existingUser.UpdatedAt = DateTime.UtcNow; if (!string.IsNullOrWhiteSpace(command.Name)) existingUser.Name ??= command.Name; var tokens = AddTokens(existingUser.Id, existingUser.Phone, existingUser.Role); await _db.SaveChangesAsync(ct); return new AuthResult(0, new { tokens.accessToken, tokens.refreshToken, user = new { existingUser.Id, existingUser.Phone, existingUser.Role, existingUser.Name, existingUser.Gender, existingUser.AvatarUrl, isNew = false } }); } // 3. 新用户 → 创建账户(无需手机号、无需选医生) var newUser = new User { Id = Guid.NewGuid(), Phone = null, AppleUserId = appleUserId, Role = "User", Name = command.Name, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow, }; _db.Users.Add(newUser); _db.NotificationPreferences.Add(new NotificationPreference { Id = Guid.NewGuid(), UserId = newUser.Id }); _db.HealthArchives.Add(new HealthArchive { Id = Guid.NewGuid(), UserId = newUser.Id }); var newTokens = AddTokens(newUser.Id, null, newUser.Role); await _db.SaveChangesAsync(ct); return new AuthResult(0, new { newTokens.accessToken, newTokens.refreshToken, user = new { newUser.Id, Phone = (string?)null, newUser.Role, newUser.Name, isNew = true } }); } private Task TakeCodeAsync(string phone, string code, CancellationToken ct) => _db.VerificationCodes.Where(x => x.Phone == phone && x.Code == code && x.ExpiresAt > DateTime.UtcNow && !x.IsUsed) .OrderByDescending(x => x.CreatedAt).FirstOrDefaultAsync(ct); private (string accessToken, string refreshToken) AddTokens(Guid userId, string? phone, string role) { var access = _jwt.GenerateAccessToken(userId, phone ?? "", role); var refresh = _jwt.GenerateRefreshToken(); _db.RefreshTokens.Add(new RefreshToken { Id = Guid.NewGuid(), UserId = userId, Token = refresh, ExpiresAt = DateTime.UtcNow.AddDays(30) }); return (access, refresh); } private static AuthResult Error(int code, string message) => new(code, null, message); }