using System.Security.Cryptography; using System.Text; using System.Text.Json; using MiAssessment.Core.Interfaces; using MiAssessment.Model.Data; using MiAssessment.Model.Entities; using MiAssessment.Model.Models.Auth; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace MiAssessment.Core.Services; /// /// ��֤����ʵ�� /// public class AuthService : IAuthService { private readonly MiAssessmentDbContext _dbContext; private readonly IUserService _userService; private readonly IJwtService _jwtService; private readonly IWechatService _wechatService; private readonly IIpLocationService _ipLocationService; private readonly IRedisService _redisService; private readonly IConfigService _configService; private readonly JwtSettings _jwtSettings; private readonly ILogger _logger; // Redis key prefixes private const string LoginDebounceKeyPrefix = "login:debounce:"; private const string SmsCodeKeyPrefix = "sms:code:"; private const int DebounceSeconds = 3; // Refresh Token ���� private const int RefreshTokenLength = 64; public AuthService( MiAssessmentDbContext dbContext, IUserService userService, IJwtService jwtService, IWechatService wechatService, IIpLocationService ipLocationService, IRedisService redisService, IConfigService configService, JwtSettings jwtSettings, ILogger logger) { _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); _userService = userService ?? throw new ArgumentNullException(nameof(userService)); _jwtService = jwtService ?? throw new ArgumentNullException(nameof(jwtService)); _wechatService = wechatService ?? throw new ArgumentNullException(nameof(wechatService)); _ipLocationService = ipLocationService ?? throw new ArgumentNullException(nameof(ipLocationService)); _redisService = redisService ?? throw new ArgumentNullException(nameof(redisService)); _configService = configService ?? throw new ArgumentNullException(nameof(configService)); _jwtSettings = jwtSettings ?? throw new ArgumentNullException(nameof(jwtSettings)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } /// /// ΢��С�����¼ /// Requirements: 1.1-1.8 /// public async Task WechatMiniProgramLoginAsync(string code, int? pid, string? clickId, string? phoneCode = null) { _logger.LogInformation("[AuthService] ΢�ŵ�¼��ʼ��code={Code}, pid={Pid}, hasPhoneCode={HasPhoneCode}", code, pid, !string.IsNullOrWhiteSpace(phoneCode)); if (string.IsNullOrWhiteSpace(code)) { _logger.LogWarning("[AuthService] ΢�ŵ�¼ʧ�ܣ�codeΪ��"); return new LoginResult { Success = false, ErrorMessage = "��Ȩcode����Ϊ��" }; } try { // 1.6 �������� - 3���ڲ������ظ���¼ var debounceKey = $"{LoginDebounceKeyPrefix}wechat:{code}"; _logger.LogInformation("[AuthService] ��������: {Key}", debounceKey); var lockAcquired = await _redisService.TryAcquireLockAsync(debounceKey, "1", TimeSpan.FromSeconds(DebounceSeconds)); if (!lockAcquired) { _logger.LogWarning("[AuthService] �����������ܾ��ظ���¼����: {Code}", code); return new LoginResult { Success = false, ErrorMessage = "����Ƶ����¼" }; } _logger.LogInformation("[AuthService] ��������ȡ�ɹ�"); // 1.1 ����΢��API��ȡopenid��unionid _logger.LogInformation("[AuthService] ��ʼ����΢��API��ȡopenid..."); var wechatResult = await _wechatService.GetOpenIdAsync(code); _logger.LogInformation("[AuthService] ΢��API������ɣ�Success={Success}, OpenId={OpenId}, UnionId={UnionId}, Error={Error}", wechatResult.Success, wechatResult.OpenId ?? "null", wechatResult.UnionId ?? "null", wechatResult.ErrorMessage ?? "null"); if (!wechatResult.Success) { _logger.LogWarning("[AuthService] ΢��API����ʧ��: {Error}", wechatResult.ErrorMessage); return new LoginResult { Success = false, ErrorMessage = wechatResult.ErrorMessage ?? "��¼ʧ�ܣ����Ժ�����" }; } var openId = wechatResult.OpenId!; var unionId = wechatResult.UnionId; // 1.2 �����û� - ����ͨ��unionid���ң����ͨ��openid���� User? user = null; if (!string.IsNullOrWhiteSpace(unionId)) { _logger.LogInformation("[AuthService] ����ͨ��unionid�����û�: {UnionId}", unionId); user = await _userService.GetUserByUnionIdAsync(unionId); _logger.LogInformation("[AuthService] unionid���ҽ��: {Found}", user != null ? $"�ҵ��û�ID={user.Id}" : "δ�ҵ�"); } if (user == null) { _logger.LogInformation("[AuthService] ����ͨ��openid�����û�: {OpenId}", openId); user = await _userService.GetUserByOpenIdAsync(openId); _logger.LogInformation("[AuthService] openid���ҽ��: {Found}", user != null ? $"�ҵ��û�ID={user.Id}" : "δ�ҵ�"); } if (user == null) { // 1.3 新用户,创建并绑定上级 _logger.LogInformation("[AuthService] 用户不存在,开始创建新用户, pid={Pid}", pid); // 通过 phoneCode 获取手机号 string? mobile = null; if (!string.IsNullOrWhiteSpace(phoneCode)) { _logger.LogInformation("[AuthService] 开始通过phoneCode获取手机号"); var mobileResult = await _wechatService.GetMobileAsync(phoneCode); if (mobileResult.Success && !string.IsNullOrWhiteSpace(mobileResult.Mobile)) { mobile = mobileResult.Mobile; _logger.LogInformation("[AuthService] 手机号获取成功"); } else { _logger.LogWarning("[AuthService] 手机号获取失败: {Error}", mobileResult.ErrorMessage); } } var createDto = new CreateUserDto { OpenId = openId, UnionId = unionId, Mobile = mobile, Nickname = await GetDefaultNicknameAsync(), Headimg = await GetDefaultAvatarAsync(openId), Pid = pid ?? 0 }; _logger.LogInformation("[AuthService] CreateUserDto.Pid={Pid}", createDto.Pid); user = await _userService.CreateUserAsync(createDto); _logger.LogInformation("[AuthService] 新用户创建成功: UserId={UserId}, ParentUserId={ParentUserId}", user.Id, user.ParentUserId); } else { // 1.4 已有用户,不绑定上级 _logger.LogInformation("[AuthService] 已有用户登录: UserId={UserId}, ParentUserId={ParentUserId}, pid参数={Pid}(忽略)", user.Id, user.ParentUserId, pid); if (string.IsNullOrWhiteSpace(user.UnionId) && !string.IsNullOrWhiteSpace(unionId)) { _logger.LogInformation("[AuthService] 更新用户unionid: UserId={UserId}", user.Id); await _userService.UpdateUserAsync(user.Id, new UpdateUserDto { UnionId = unionId }); _logger.LogInformation("[AuthService] unionid更新成功"); } // 已有用户如果没有手机号,也尝试通过 phoneCode 补充 if (string.IsNullOrWhiteSpace(user.Phone) && !string.IsNullOrWhiteSpace(phoneCode)) { _logger.LogInformation("[AuthService] 已有用户缺少手机号,尝试通过phoneCode补充"); var mobileResult = await _wechatService.GetMobileAsync(phoneCode); if (mobileResult.Success && !string.IsNullOrWhiteSpace(mobileResult.Mobile)) { await _userService.UpdateUserAsync(user.Id, new UpdateUserDto { Mobile = mobileResult.Mobile }); user.Phone = mobileResult.Mobile; _logger.LogInformation("[AuthService] 手机号补充成功"); } } } // 1.5 ����˫ Token��Access Token + Refresh Token�� _logger.LogInformation("[AuthService] ��ʼ����˫ Token: UserId={UserId}", user.Id); var loginResponse = await GenerateLoginResponseAsync(user, null); _logger.LogInformation("[AuthService] ˫ Token ���ɳɹ���AccessToken����={Length}", loginResponse.AccessToken?.Length ?? 0); _logger.LogInformation("[AuthService] ΢�ŵ�¼�ɹ�: UserId={UserId}", user.Id); return new LoginResult { Success = true, Token = loginResponse.AccessToken, // ���ݾɰ� UserId = user.Id, LoginResponse = loginResponse }; } catch (Exception ex) { _logger.LogError(ex, "[AuthService] ΢�ŵ�¼�쳣: code={Code}, Message={Message}, StackTrace={StackTrace}", code, ex.Message, ex.StackTrace); return new LoginResult { Success = false, ErrorMessage = "������ϣ����Ժ�����" }; } } /// /// �ֻ�����֤���¼ /// Requirements: 2.1-2.7 /// public async Task MobileLoginAsync(string mobile, string code, int? pid, string? clickId) { if (string.IsNullOrWhiteSpace(mobile)) { return new LoginResult { Success = false, ErrorMessage = "�ֻ��Ų���Ϊ��" }; } if (string.IsNullOrWhiteSpace(code)) { return new LoginResult { Success = false, ErrorMessage = "��֤�벻��Ϊ��" }; } try { // 2.6 �������� - 3���ڲ������ظ���¼ var debounceKey = $"{LoginDebounceKeyPrefix}mobile:{mobile}"; var lockAcquired = await _redisService.TryAcquireLockAsync(debounceKey, "1", TimeSpan.FromSeconds(DebounceSeconds)); if (!lockAcquired) { _logger.LogWarning("Login debounce triggered for mobile: {Mobile}", MaskMobile(mobile)); return new LoginResult { Success = false, ErrorMessage = "����Ƶ����¼" }; } // 2.1 ��Redis��ȡ����֤��֤�� var smsCodeKey = $"{SmsCodeKeyPrefix}{mobile}"; var storedCode = await _redisService.GetStringAsync(smsCodeKey); if (string.IsNullOrWhiteSpace(storedCode) || storedCode != code) { _logger.LogWarning("SMS code verification failed for mobile: {Mobile}", MaskMobile(mobile)); return new LoginResult { Success = false, ErrorMessage = "��֤�����" }; } // 2.2 ��֤����֤ͨ����ɾ��Redis�е���֤�� await _redisService.DeleteAsync(smsCodeKey); // �����û� var user = await _userService.GetUserByMobileAsync(mobile); if (user == null) { // 2.3 �û������ڣ��������û� var createDto = new CreateUserDto { Mobile = mobile, Nickname = await GetDefaultNicknameAsync(), Headimg = await GetDefaultAvatarAsync(mobile), Pid = pid ?? 0 }; user = await _userService.CreateUserAsync(createDto); _logger.LogInformation("New user created via mobile login: UserId={UserId}, Mobile={Mobile}", user.Id, MaskMobile(mobile)); } // 2.4 ����˫ Token��Access Token + Refresh Token�� var loginResponse = await GenerateLoginResponseAsync(user, null); _logger.LogInformation("Mobile login successful: UserId={UserId}", user.Id); return new LoginResult { Success = true, Token = loginResponse.AccessToken, // ���ݾɰ� UserId = user.Id, LoginResponse = loginResponse }; } catch (Exception ex) { _logger.LogError(ex, "Mobile login failed for mobile: {Mobile}", MaskMobile(mobile)); return new LoginResult { Success = false, ErrorMessage = "������ϣ����Ժ�����" }; } } /// /// ��֤����ֻ��� /// Requirements: 5.1-5.5 /// public async Task BindMobileAsync(long userId, string mobile, string code) { if (string.IsNullOrWhiteSpace(mobile)) { throw new ArgumentException("�ֻ��Ų���Ϊ��", nameof(mobile)); } if (string.IsNullOrWhiteSpace(code)) { throw new ArgumentException("��֤�벻��Ϊ��", nameof(code)); } // 5.1 ��֤������֤�� var smsCodeKey = $"{SmsCodeKeyPrefix}{mobile}"; var storedCode = await _redisService.GetStringAsync(smsCodeKey); if (string.IsNullOrWhiteSpace(storedCode) || storedCode != code) { _logger.LogWarning("SMS code verification failed for bind mobile: UserId={UserId}, Mobile={Mobile}", userId, MaskMobile(mobile)); throw new InvalidOperationException("��֤�����"); } // ��֤����֤ͨ����ɾ�� await _redisService.DeleteAsync(smsCodeKey); // ��ȡ��ǰ�û� var currentUser = await _userService.GetUserByIdAsync(userId); if (currentUser == null) { throw new InvalidOperationException("�û�������"); } // ����ֻ����Ƿ��ѱ������û��� var existingUser = await _userService.GetUserByMobileAsync(mobile); if (existingUser != null && existingUser.Id != userId) { // 5.2 �ֻ����ѱ������û��󶨣���Ҫ�ϲ��˻� return await MergeAccountsAsync(currentUser, existingUser); } // 5.4 �ֻ���δ���󶨣�ֱ�Ӹ��µ�ǰ�û����ֻ��� await _userService.UpdateUserAsync(userId, new UpdateUserDto { Mobile = mobile }); _logger.LogInformation("Mobile bound successfully: UserId={UserId}, Mobile={Mobile}", userId, MaskMobile(mobile)); return new BindMobileResponse { Token = null }; } /// /// ΢����Ȩ���ֻ��� /// Requirements: 5.1-5.5 /// public async Task WechatBindMobileAsync(long userId, string wechatCode) { if (string.IsNullOrWhiteSpace(wechatCode)) { throw new ArgumentException("΢����Ȩcode����Ϊ��", nameof(wechatCode)); } // ����΢��API��ȡ�ֻ��� var mobileResult = await _wechatService.GetMobileAsync(wechatCode); if (!mobileResult.Success || string.IsNullOrWhiteSpace(mobileResult.Mobile)) { _logger.LogWarning("WeChat get mobile failed: UserId={UserId}, Error={Error}", userId, mobileResult.ErrorMessage); throw new InvalidOperationException(mobileResult.ErrorMessage ?? "��ȡ�ֻ���ʧ��"); } var mobile = mobileResult.Mobile; // ��ȡ��ǰ�û� var currentUser = await _userService.GetUserByIdAsync(userId); if (currentUser == null) { throw new InvalidOperationException("�û�������"); } // ����ֻ����Ƿ��ѱ������û��� var existingUser = await _userService.GetUserByMobileAsync(mobile); if (existingUser != null && existingUser.Id != userId) { // 5.2 �ֻ����ѱ������û��󶨣���Ҫ�ϲ��˻� return await MergeAccountsAsync(currentUser, existingUser); } // 5.4 �ֻ���δ���󶨣�ֱ�Ӹ��µ�ǰ�û����ֻ��� await _userService.UpdateUserAsync(userId, new UpdateUserDto { Mobile = mobile }); _logger.LogInformation("Mobile bound via WeChat successfully: UserId={UserId}, Mobile={Mobile}", userId, MaskMobile(mobile)); return new BindMobileResponse { Token = null }; } /// /// ��¼��¼��Ϣ /// Requirements: 6.1, 6.3, 6.4 /// public async Task RecordLoginAsync(long userId, string? device, string? deviceInfo) { var user = await _userService.GetUserByIdAsync(userId); if (user == null) { throw new InvalidOperationException("�û�������"); } try { // ��ȡ�ͻ���IP������ʹ�ÿ��ַ�����Ϊռλ����ʵ��IPӦ��Controller���룩 var clientIp = deviceInfo ?? string.Empty; var now = DateTime.Now; // 6.1 ��¼��¼��־ var loginLog = new UserLoginLog { UserId = userId, LoginType = "wechat", LoginIp = clientIp, UserAgent = device, Platform = "miniprogram", Status = 1, CreateTime = now }; await _dbContext.UserLoginLogs.AddAsync(loginLog); // �����û�����¼ʱ�� user.LastLoginTime = now; user.LastLoginIp = clientIp; _dbContext.Users.Update(user); await _dbContext.SaveChangesAsync(); _logger.LogInformation("Login recorded: UserId={UserId}, Device={Device}, IP={IP}", userId, device, clientIp); // 6.4 �����û���uid���dzƺ�ͷ�� return new RecordLoginResponse { Uid = user.Uid, Nickname = user.Nickname, Headimg = user.Avatar }; } catch (Exception ex) { _logger.LogError(ex, "Failed to record login: UserId={UserId}", userId); throw; } } /// /// H5���ֻ��ţ�������֤�룩 /// Requirements: 13.1 /// public async Task BindMobileH5Async(long userId, string mobile) { if (string.IsNullOrWhiteSpace(mobile)) { throw new ArgumentException("�ֻ��Ų���Ϊ��", nameof(mobile)); } // ��ȡ��ǰ�û� var currentUser = await _userService.GetUserByIdAsync(userId); if (currentUser == null) { throw new InvalidOperationException("�û�������"); } // ����ֻ����Ƿ��ѱ������û��� var existingUser = await _userService.GetUserByMobileAsync(mobile); if (existingUser != null && existingUser.Id != userId) { // �ֻ����ѱ������û��󶨣���Ҫ�ϲ��˻� return await MergeAccountsAsync(currentUser, existingUser); } // �ֻ���δ���󶨣�ֱ�Ӹ��µ�ǰ�û����ֻ��� await _userService.UpdateUserAsync(userId, new UpdateUserDto { Mobile = mobile }); _logger.LogInformation("H5 Mobile bound successfully: UserId={UserId}, Mobile={Mobile}", userId, MaskMobile(mobile)); return new BindMobileResponse { Token = null }; } /// /// �˺�ע�� /// Requirements: 7.1-7.3 /// public async Task LogOffAsync(long userId, int type) { var user = await _userService.GetUserByIdAsync(userId); if (user == null) { throw new InvalidOperationException("�û�������"); } try { // 7.1 ��¼ע��������־ var action = type == 0 ? "ע���˺�" : "ȡ��ע��"; _logger.LogInformation("User log off request: UserId={UserId}, Type={Type}, Action={Action}", userId, type, action); // ����������Ӹ����ע���߼������磺 // - ���û�״̬����Ϊ��ע�� // - �����û���صĻ��� // - ����֪ͨ�� if (type == 0) { // ע���˺� - ���������û�״̬Ϊ���� user.Status = 0; _dbContext.Users.Update(user); await _dbContext.SaveChangesAsync(); _logger.LogInformation("User account deactivated: UserId={UserId}", userId); } else if (type == 1) { // ȡ��ע�� - �ָ��û�״̬ user.Status = 1; _dbContext.Users.Update(user); await _dbContext.SaveChangesAsync(); _logger.LogInformation("User account reactivated: UserId={UserId}", userId); } // 7.2 ����ע���ɹ�����Ϣ��ͨ�����׳��쳣����ʾ�ɹ��� } catch (Exception ex) { _logger.LogError(ex, "Failed to process log off: UserId={UserId}, Type={Type}", userId, type); throw; } } #region Refresh Token Methods /// /// ���� Refresh Token ���洢�����ݿ� /// Requirements: 1.4, 1.5, 4.1 /// /// �û�ID /// �ͻ��� IP ��ַ /// ���ɵ� Refresh Token ���� private async Task GenerateRefreshTokenAsync(long userId, string? ipAddress) { // ������� Refresh Token var refreshToken = GenerateSecureRandomString(RefreshTokenLength); // ���� SHA256 ��ϣֵ���ڴ洢 var tokenHash = ComputeSha256Hash(refreshToken); // �������ʱ�䣨7�죩 var expiresAt = DateTime.Now.AddDays(_jwtSettings.RefreshTokenExpirationDays); // �������ݿ��¼ var userRefreshToken = new UserRefreshToken { UserId = userId, TokenHash = tokenHash, ExpiresAt = expiresAt, CreatedAt = DateTime.Now, CreatedByIp = ipAddress }; await _dbContext.UserRefreshTokens.AddAsync(userRefreshToken); await _dbContext.SaveChangesAsync(); _logger.LogInformation("Generated refresh token for user {UserId}, expires at {ExpiresAt}", userId, expiresAt); return refreshToken; } /// /// ���ɵ�¼��Ӧ������˫ Token�� /// Requirements: 1.1, 1.2, 1.3, 1.4, 1.5 /// /// �û�ʵ�� /// �ͻ��� IP ��ַ /// ��¼��Ӧ private async Task GenerateLoginResponseAsync(User user, string? ipAddress) { // ���� Access Token (JWT) var accessToken = _jwtService.GenerateToken(user); // ���� Refresh Token ���洢 var refreshToken = await GenerateRefreshTokenAsync(user.Id, ipAddress); // ���� Access Token ����ʱ�䣨�룩 var expiresIn = _jwtSettings.ExpirationMinutes * 60; return new LoginResponse { AccessToken = accessToken, RefreshToken = refreshToken, ExpiresIn = expiresIn, UserId = user.Id }; } /// /// ˢ�� Token /// Requirements: 2.1-2.6 /// public async Task RefreshTokenAsync(string refreshToken, string? ipAddress) { if (string.IsNullOrWhiteSpace(refreshToken)) { _logger.LogWarning("Refresh token is empty"); return RefreshTokenResult.Fail("ˢ�����Ʋ���Ϊ��"); } try { // ���� Token ��ϣֵ var tokenHash = ComputeSha256Hash(refreshToken); // ���� Token ��¼ var storedToken = await _dbContext.UserRefreshTokens .Include(t => t.User) .FirstOrDefaultAsync(t => t.TokenHash == tokenHash); if (storedToken == null) { _logger.LogWarning("Refresh token not found: {TokenHash}", tokenHash.Substring(0, 8) + "..."); return RefreshTokenResult.Fail("��Ч��ˢ������"); } // ����Ƿ��ѹ��� if (storedToken.IsExpired) { _logger.LogWarning("Refresh token expired for user {UserId}", storedToken.UserId); return RefreshTokenResult.Fail("ˢ�������ѹ���"); } // ����Ƿ��ѳ��� if (storedToken.IsRevoked) { _logger.LogWarning("Refresh token revoked for user {UserId}", storedToken.UserId); return RefreshTokenResult.Fail("ˢ��������ʧЧ"); } // ����û��Ƿ��������Ч var user = storedToken.User; if (user == null) { _logger.LogWarning("User not found for refresh token"); return RefreshTokenResult.Fail("�û�������"); } if (user.Status == 0) { _logger.LogWarning("User {UserId} is disabled", user.Id); return RefreshTokenResult.Fail("�˺��ѱ�����"); } // Token �ֻ��������µ� Refresh Token var newRefreshToken = GenerateSecureRandomString(RefreshTokenLength); var newTokenHash = ComputeSha256Hash(newRefreshToken); // ������ Token ����¼������ϵ storedToken.RevokedAt = DateTime.Now; storedToken.RevokedByIp = ipAddress; storedToken.ReplacedByToken = newTokenHash; // �����µ� Token ��¼ var newUserRefreshToken = new UserRefreshToken { UserId = user.Id, TokenHash = newTokenHash, ExpiresAt = DateTime.Now.AddDays(_jwtSettings.RefreshTokenExpirationDays), CreatedAt = DateTime.Now, CreatedByIp = ipAddress }; await _dbContext.UserRefreshTokens.AddAsync(newUserRefreshToken); await _dbContext.SaveChangesAsync(); // �����µ� Access Token var accessToken = _jwtService.GenerateToken(user); var expiresIn = _jwtSettings.ExpirationMinutes * 60; _logger.LogInformation("Token refreshed successfully for user {UserId}", user.Id); return RefreshTokenResult.Ok(new LoginResponse { AccessToken = accessToken, RefreshToken = newRefreshToken, ExpiresIn = expiresIn, UserId = user.Id }); } catch (Exception ex) { _logger.LogError(ex, "Error refreshing token"); return RefreshTokenResult.Fail("ˢ������ʧ�ܣ����Ժ�����"); } } /// /// ���� Token /// Requirements: 4.4 /// public async Task RevokeTokenAsync(string refreshToken, string? ipAddress) { if (string.IsNullOrWhiteSpace(refreshToken)) { _logger.LogWarning("Cannot revoke empty refresh token"); return; } try { // ���� Token ��ϣֵ var tokenHash = ComputeSha256Hash(refreshToken); // ���� Token ��¼ var storedToken = await _dbContext.UserRefreshTokens .FirstOrDefaultAsync(t => t.TokenHash == tokenHash); if (storedToken == null) { _logger.LogWarning("Refresh token not found for revocation"); return; } // ����Ѿ�������ֱ�ӷ��� if (storedToken.IsRevoked) { _logger.LogInformation("Refresh token already revoked"); return; } // ���� Token storedToken.RevokedAt = DateTime.Now; storedToken.RevokedByIp = ipAddress; await _dbContext.SaveChangesAsync(); _logger.LogInformation("Refresh token revoked for user {UserId}", storedToken.UserId); } catch (Exception ex) { _logger.LogError(ex, "Error revoking refresh token"); throw; } } /// /// �����û������� Token /// Requirements: 4.4 /// public async Task RevokeAllUserTokensAsync(long userId, string? ipAddress) { try { // �����û�������Ч�� Token var activeTokens = await _dbContext.UserRefreshTokens .Where(t => t.UserId == userId && t.RevokedAt == null) .ToListAsync(); if (!activeTokens.Any()) { _logger.LogInformation("No active tokens found for user {UserId}", userId); return; } var now = DateTime.Now; foreach (var token in activeTokens) { token.RevokedAt = now; token.RevokedByIp = ipAddress; } await _dbContext.SaveChangesAsync(); _logger.LogInformation("Revoked {Count} tokens for user {UserId}", activeTokens.Count, userId); } catch (Exception ex) { _logger.LogError(ex, "Error revoking all tokens for user {UserId}", userId); throw; } } #endregion #region Private Helper Methods /// /// �ϲ��˻� - ����ǰ�û���openidǨ�Ƶ��ֻ����û� /// private async Task MergeAccountsAsync(User currentUser, User mobileUser) { using var transaction = await _dbContext.Database.BeginTransactionAsync(); try { _logger.LogInformation("Merging accounts: CurrentUserId={CurrentUserId}, MobileUserId={MobileUserId}", currentUser.Id, mobileUser.Id); // 5.2 ����ǰ�û���openidǨ�Ƶ��ֻ����û� if (!string.IsNullOrWhiteSpace(currentUser.OpenId)) { mobileUser.OpenId = currentUser.OpenId; } if (!string.IsNullOrWhiteSpace(currentUser.UnionId)) { mobileUser.UnionId = currentUser.UnionId; } mobileUser.UpdateTime = DateTime.Now; _dbContext.Users.Update(mobileUser); // ɾ����ǰ�û� _dbContext.Users.Remove(currentUser); await _dbContext.SaveChangesAsync(); await transaction.CommitAsync(); // 5.3 �����µ�token var newToken = _jwtService.GenerateToken(mobileUser); _logger.LogInformation("Accounts merged successfully: NewUserId={NewUserId}", mobileUser.Id); return new BindMobileResponse { Token = newToken }; } catch (Exception ex) { await transaction.RollbackAsync(); _logger.LogError(ex, "Failed to merge accounts: CurrentUserId={CurrentUserId}, MobileUserId={MobileUserId}", currentUser.Id, mobileUser.Id); throw; } } /// /// 获取用户默认昵称(从配置读取前缀) /// private async Task GetDefaultNicknameAsync() { try { var configJson = await _configService.GetConfigValueAsync("user_config"); if (!string.IsNullOrEmpty(configJson)) { var config = JsonSerializer.Deserialize(configJson); if (config.TryGetProperty("default_nickname_prefix", out var prefixEl) && prefixEl.ValueKind == JsonValueKind.String && !string.IsNullOrWhiteSpace(prefixEl.GetString())) { return $"{prefixEl.GetString()}{Random.Shared.Next(100000, 999999)}"; } } } catch (Exception ex) { _logger.LogWarning(ex, "读取用户默认昵称配置失败,使用默认值"); } return $"用户{Random.Shared.Next(100000, 999999)}"; } /// /// 获取用户默认头像(从配置读取,为空则自动生成) /// private async Task GetDefaultAvatarAsync(string seed) { try { var configJson = await _configService.GetConfigValueAsync("user_config"); if (!string.IsNullOrEmpty(configJson)) { var config = JsonSerializer.Deserialize(configJson); if (config.TryGetProperty("default_avatar", out var avatarEl) && avatarEl.ValueKind == JsonValueKind.String && !string.IsNullOrWhiteSpace(avatarEl.GetString())) { return avatarEl.GetString()!; } } } catch (Exception ex) { _logger.LogWarning(ex, "读取用户默认头像配置失败,使用系统生成"); } return GenerateDefaultAvatar(seed); } /// /// ����Ĭ��ͷ��URL /// private static string GenerateDefaultAvatar(string seed) { // ʹ����������һ���򵥵�Ĭ��ͷ��URL // ʵ����Ŀ�п���ʹ��Identicon�������ͷ�����ɷ��� var hash = ComputeMd5(seed); return $"https://api.dicebear.com/7.x/identicon/svg?seed={hash}"; } /// /// ����MD5��ϣ /// private static string ComputeMd5(string input) { var inputBytes = Encoding.UTF8.GetBytes(input); var hashBytes = MD5.HashData(inputBytes); return Convert.ToHexString(hashBytes).ToLowerInvariant(); } /// /// ��������ַ��� /// private static string GenerateRandomString(int length) { const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; var result = new char[length]; for (int i = 0; i < length; i++) { result[i] = chars[Random.Shared.Next(chars.Length)]; } return new string(result); } /// /// �����ֻ��� /// private static string MaskMobile(string mobile) { if (string.IsNullOrWhiteSpace(mobile) || mobile.Length < 7) return "***"; return $"{mobile.Substring(0, 3)}****{mobile.Substring(mobile.Length - 4)}"; } /// /// ��ȡ����е����� /// private static int GetWeekOfYear(DateTime date) { var cal = System.Globalization.CultureInfo.CurrentCulture.Calendar; return cal.GetWeekOfYear(date, System.Globalization.CalendarWeekRule.FirstDay, DayOfWeek.Monday); } /// /// ���� SHA256 ��ϣֵ /// Requirements: 4.1 /// private static string ComputeSha256Hash(string input) { var inputBytes = Encoding.UTF8.GetBytes(input); var hashBytes = SHA256.HashData(inputBytes); return Convert.ToHexString(hashBytes).ToLowerInvariant(); } /// /// ���ɰ�ȫ������ַ��������� Refresh Token�� /// private static string GenerateSecureRandomString(int length) { var randomBytes = new byte[length]; using var rng = RandomNumberGenerator.Create(); rng.GetBytes(randomBytes); return Convert.ToBase64String(randomBytes) .Replace("+", "-") .Replace("/", "_") .Replace("=", "") .Substring(0, length); } #endregion }