This commit is contained in:
parent
d0b0564636
commit
c54f049409
|
|
@ -11,7 +11,7 @@ using Microsoft.Extensions.Logging;
|
|||
namespace MiAssessment.Core.Services;
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʵ<EFBFBD><EFBFBD>
|
||||
/// 认证服务实现
|
||||
/// </summary>
|
||||
public class AuthService : IAuthService
|
||||
{
|
||||
|
|
@ -30,7 +30,7 @@ public class AuthService : IAuthService
|
|||
private const string SmsCodeKeyPrefix = "sms:code:";
|
||||
private const int DebounceSeconds = 3;
|
||||
|
||||
// Refresh Token <EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
// Refresh Token 长度
|
||||
private const int RefreshTokenLength = 64;
|
||||
|
||||
public AuthService(
|
||||
|
|
@ -57,44 +57,44 @@ public class AuthService : IAuthService
|
|||
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD>С<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>¼
|
||||
/// 微信小程序登录
|
||||
/// Requirements: 1.1-1.8
|
||||
/// </summary>
|
||||
public async Task<LoginResult> WechatMiniProgramLoginAsync(string code, int? pid, string? clickId, string? phoneCode = null)
|
||||
{
|
||||
_logger.LogInformation("[AuthService] <EFBFBD>ŵ<EFBFBD>¼<EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD>code={Code}, pid={Pid}, hasPhoneCode={HasPhoneCode}", code, pid, !string.IsNullOrWhiteSpace(phoneCode));
|
||||
_logger.LogInformation("[AuthService] 微信登录开始,code={Code}, pid={Pid}, hasPhoneCode={HasPhoneCode}", code, pid, !string.IsNullOrWhiteSpace(phoneCode));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(code))
|
||||
{
|
||||
_logger.LogWarning("[AuthService] <EFBFBD>ŵ<EFBFBD>¼ʧ<EFBFBD>ܣ<EFBFBD>codeΪ<EFBFBD><EFBFBD>");
|
||||
_logger.LogWarning("[AuthService] 微信登录失败,code为空");
|
||||
return new LoginResult
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = "<EFBFBD><EFBFBD>Ȩcode<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD>"
|
||||
ErrorMessage = "授权code不能为空"
|
||||
};
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 1.6 <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> - 3<><33><EFBFBD>ڲ<EFBFBD><DAB2><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ظ<EFBFBD><D8B8><EFBFBD>¼
|
||||
// 1.6 防抖处理 - 3秒内不允许重复登录
|
||||
var debounceKey = $"{LoginDebounceKeyPrefix}wechat:{code}";
|
||||
_logger.LogInformation("[AuthService] <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: {Key}", debounceKey);
|
||||
_logger.LogInformation("[AuthService] 防抖检查: {Key}", debounceKey);
|
||||
var lockAcquired = await _redisService.TryAcquireLockAsync(debounceKey, "1", TimeSpan.FromSeconds(DebounceSeconds));
|
||||
if (!lockAcquired)
|
||||
{
|
||||
_logger.LogWarning("[AuthService] <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ܾ<EFBFBD><EFBFBD>ظ<EFBFBD><EFBFBD><EFBFBD>¼<EFBFBD><EFBFBD><EFBFBD><EFBFBD>: {Code}", code);
|
||||
_logger.LogWarning("[AuthService] 防抖处理:拒绝重复登录请求: {Code}", code);
|
||||
return new LoginResult
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = "<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƶ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>¼"
|
||||
ErrorMessage = "请勿频繁登录"
|
||||
};
|
||||
}
|
||||
_logger.LogInformation("[AuthService] <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȡ<EFBFBD>ɹ<EFBFBD>");
|
||||
_logger.LogInformation("[AuthService] 防抖锁获取成功");
|
||||
|
||||
// 1.1 <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>API<EFBFBD><EFBFBD>ȡopenid<EFBFBD><EFBFBD>unionid
|
||||
_logger.LogInformation("[AuthService] <EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>API<EFBFBD><EFBFBD>ȡopenid...");
|
||||
// 1.1 调用微信API获取openid和unionid
|
||||
_logger.LogInformation("[AuthService] 开始调用微信API获取openid...");
|
||||
var wechatResult = await _wechatService.GetOpenIdAsync(code);
|
||||
_logger.LogInformation("[AuthService] <EFBFBD><EFBFBD>API<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɣ<EFBFBD>Success={Success}, OpenId={OpenId}, UnionId={UnionId}, Error={Error}",
|
||||
_logger.LogInformation("[AuthService] 微信API调用完成:Success={Success}, OpenId={OpenId}, UnionId={UnionId}, Error={Error}",
|
||||
wechatResult.Success,
|
||||
wechatResult.OpenId ?? "null",
|
||||
wechatResult.UnionId ?? "null",
|
||||
|
|
@ -102,30 +102,30 @@ public class AuthService : IAuthService
|
|||
|
||||
if (!wechatResult.Success)
|
||||
{
|
||||
_logger.LogWarning("[AuthService] <EFBFBD><EFBFBD>API<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD><EFBFBD>: {Error}", wechatResult.ErrorMessage);
|
||||
_logger.LogWarning("[AuthService] 微信API调用失败: {Error}", wechatResult.ErrorMessage);
|
||||
return new LoginResult
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = wechatResult.ErrorMessage ?? "<EFBFBD><EFBFBD>¼ʧ<EFBFBD>ܣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ժ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>"
|
||||
ErrorMessage = wechatResult.ErrorMessage ?? "登录失败,请稍后重试"
|
||||
};
|
||||
}
|
||||
|
||||
var openId = wechatResult.OpenId!;
|
||||
var unionId = wechatResult.UnionId;
|
||||
|
||||
// 1.2 <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD> - <20><><EFBFBD><EFBFBD>ͨ<EFBFBD><CDA8>unionid<69><64><EFBFBD>ң<EFBFBD><D2A3><EFBFBD><EFBFBD>ͨ<EFBFBD><CDA8>openid<69><64><EFBFBD><EFBFBD>
|
||||
// 1.2 查找用户 - 优先通过unionid查找,再通过openid查找
|
||||
User? user = null;
|
||||
if (!string.IsNullOrWhiteSpace(unionId))
|
||||
{
|
||||
_logger.LogInformation("[AuthService] <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͨ<EFBFBD><EFBFBD>unionid<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD>: {UnionId}", unionId);
|
||||
_logger.LogInformation("[AuthService] 尝试通过unionid查找用户: {UnionId}", unionId);
|
||||
user = await _userService.GetUserByUnionIdAsync(unionId);
|
||||
_logger.LogInformation("[AuthService] unionid<EFBFBD><EFBFBD><EFBFBD>ҽ<EFBFBD><EFBFBD>: {Found}", user != null ? $"<22>ҵ<EFBFBD><D2B5>û<EFBFBD>ID={user.Id}" : "δ<>ҵ<EFBFBD>");
|
||||
_logger.LogInformation("[AuthService] unionid查找结果: {Found}", user != null ? $"找到用户ID={user.Id}" : "未找到");
|
||||
}
|
||||
if (user == null)
|
||||
{
|
||||
_logger.LogInformation("[AuthService] <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͨ<EFBFBD><EFBFBD>openid<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD>: {OpenId}", openId);
|
||||
_logger.LogInformation("[AuthService] 尝试通过openid查找用户: {OpenId}", openId);
|
||||
user = await _userService.GetUserByOpenIdAsync(openId);
|
||||
_logger.LogInformation("[AuthService] openid<EFBFBD><EFBFBD><EFBFBD>ҽ<EFBFBD><EFBFBD>: {Found}", user != null ? $"<22>ҵ<EFBFBD><D2B5>û<EFBFBD>ID={user.Id}" : "δ<>ҵ<EFBFBD>");
|
||||
_logger.LogInformation("[AuthService] openid查找结果: {Found}", user != null ? $"找到用户ID={user.Id}" : "未找到");
|
||||
}
|
||||
|
||||
if (user == null)
|
||||
|
|
@ -189,36 +189,42 @@ public class AuthService : IAuthService
|
|||
}
|
||||
}
|
||||
|
||||
// 1.5 <EFBFBD><EFBFBD><EFBFBD><EFBFBD>˫ Token<65><6E>Access Token + Refresh Token<65><6E>
|
||||
_logger.LogInformation("[AuthService] <EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>˫ Token: UserId={UserId}", user.Id);
|
||||
// 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 <20><><EFBFBD>ɳɹ<C9B3><C9B9><EFBFBD>AccessToken<65><6E><EFBFBD><EFBFBD>={Length}", loginResponse.AccessToken?.Length ?? 0);
|
||||
|
||||
_logger.LogInformation("[AuthService] <>ŵ<EFBFBD>¼<EFBFBD>ɹ<EFBFBD>: UserId={UserId}", user.Id);
|
||||
// 更新最后登录时间
|
||||
user.LastLoginTime = DateTime.Now;
|
||||
_dbContext.Users.Update(user);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
_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, // <20><><EFBFBD>ݾɰ<DDBE>
|
||||
Token = loginResponse.AccessToken, // 兼容旧版
|
||||
UserId = user.Id,
|
||||
LoginResponse = loginResponse
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "[AuthService] <EFBFBD>ŵ<EFBFBD>¼<EFBFBD>쳣: code={Code}, Message={Message}, StackTrace={StackTrace}",
|
||||
_logger.LogError(ex, "[AuthService] 微信登录异常: code={Code}, Message={Message}, StackTrace={StackTrace}",
|
||||
code, ex.Message, ex.StackTrace);
|
||||
return new LoginResult
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = "<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ժ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>"
|
||||
ErrorMessage = "服务器异常,请稍后重试"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD>¼
|
||||
/// 手机号验证码登录
|
||||
/// Requirements: 2.1-2.7
|
||||
/// </summary>
|
||||
public async Task<LoginResult> MobileLoginAsync(string mobile, string code, int? pid, string? clickId)
|
||||
|
|
@ -228,7 +234,7 @@ public class AuthService : IAuthService
|
|||
return new LoginResult
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = "<EFBFBD>ֻ<EFBFBD><EFBFBD>Ų<EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD>"
|
||||
ErrorMessage = "手机号不能为空"
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -237,13 +243,13 @@ public class AuthService : IAuthService
|
|||
return new LoginResult
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = "<EFBFBD><EFBFBD>֤<EFBFBD>벻<EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD>"
|
||||
ErrorMessage = "验证码不能为空"
|
||||
};
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 2.6 <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> - 3<><33><EFBFBD>ڲ<EFBFBD><DAB2><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ظ<EFBFBD><D8B8><EFBFBD>¼
|
||||
// 2.6 防抖处理 - 3秒内不允许重复登录
|
||||
var debounceKey = $"{LoginDebounceKeyPrefix}mobile:{mobile}";
|
||||
var lockAcquired = await _redisService.TryAcquireLockAsync(debounceKey, "1", TimeSpan.FromSeconds(DebounceSeconds));
|
||||
if (!lockAcquired)
|
||||
|
|
@ -252,11 +258,11 @@ public class AuthService : IAuthService
|
|||
return new LoginResult
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = "<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƶ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>¼"
|
||||
ErrorMessage = "请勿频繁登录"
|
||||
};
|
||||
}
|
||||
|
||||
// 2.1 <EFBFBD><EFBFBD>Redis<EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD>
|
||||
// 2.1 从Redis获取短信验证码验证
|
||||
var smsCodeKey = $"{SmsCodeKeyPrefix}{mobile}";
|
||||
var storedCode = await _redisService.GetStringAsync(smsCodeKey);
|
||||
|
||||
|
|
@ -266,19 +272,19 @@ public class AuthService : IAuthService
|
|||
return new LoginResult
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = "<EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>"
|
||||
ErrorMessage = "验证码错误"
|
||||
};
|
||||
}
|
||||
|
||||
// 2.2 <EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤ͨ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɾ<EFBFBD><EFBFBD>Redis<EFBFBD>е<EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD>
|
||||
// 2.2 验证码验证通过,删除Redis中的验证码
|
||||
await _redisService.DeleteAsync(smsCodeKey);
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD>
|
||||
// 查找用户
|
||||
var user = await _userService.GetUserByMobileAsync(mobile);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
// 2.3 <EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD>
|
||||
// 2.3 用户不存在,创建新用户
|
||||
var createDto = new CreateUserDto
|
||||
{
|
||||
Mobile = mobile,
|
||||
|
|
@ -291,15 +297,20 @@ public class AuthService : IAuthService
|
|||
_logger.LogInformation("New user created via mobile login: UserId={UserId}, Mobile={Mobile}", user.Id, MaskMobile(mobile));
|
||||
}
|
||||
|
||||
// 2.4 <EFBFBD><EFBFBD><EFBFBD><EFBFBD>˫ Token<65><6E>Access Token + Refresh Token<65><6E>
|
||||
// 2.4 生成双 Token(Access Token + Refresh Token)
|
||||
var loginResponse = await GenerateLoginResponseAsync(user, null);
|
||||
|
||||
// 更新最后登录时间
|
||||
user.LastLoginTime = DateTime.Now;
|
||||
_dbContext.Users.Update(user);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Mobile login successful: UserId={UserId}", user.Id);
|
||||
|
||||
return new LoginResult
|
||||
{
|
||||
Success = true,
|
||||
Token = loginResponse.AccessToken, // <EFBFBD><EFBFBD><EFBFBD>ݾɰ<EFBFBD>
|
||||
Token = loginResponse.AccessToken, // 兼容旧版
|
||||
UserId = user.Id,
|
||||
LoginResponse = loginResponse
|
||||
};
|
||||
|
|
@ -310,58 +321,58 @@ public class AuthService : IAuthService
|
|||
return new LoginResult
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = "<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ժ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>"
|
||||
ErrorMessage = "服务器异常,请稍后重试"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD>
|
||||
/// 验证码绑定手机号
|
||||
/// Requirements: 5.1-5.5
|
||||
/// </summary>
|
||||
public async Task<BindMobileResponse> BindMobileAsync(long userId, string mobile, string code)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(mobile))
|
||||
{
|
||||
throw new ArgumentException("<EFBFBD>ֻ<EFBFBD><EFBFBD>Ų<EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD>", nameof(mobile));
|
||||
throw new ArgumentException("手机号不能为空", nameof(mobile));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(code))
|
||||
{
|
||||
throw new ArgumentException("<EFBFBD><EFBFBD>֤<EFBFBD>벻<EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD>", nameof(code));
|
||||
throw new ArgumentException("验证码不能为空", nameof(code));
|
||||
}
|
||||
|
||||
// 5.1 <EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD>
|
||||
// 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("<EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
throw new InvalidOperationException("验证码错误");
|
||||
}
|
||||
|
||||
// <EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤ͨ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɾ<EFBFBD><EFBFBD>
|
||||
// 验证码验证通过,删除
|
||||
await _redisService.DeleteAsync(smsCodeKey);
|
||||
|
||||
// <EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD>ǰ<EFBFBD>û<EFBFBD>
|
||||
// 获取当前用户
|
||||
var currentUser = await _userService.GetUserByIdAsync(userId);
|
||||
if (currentUser == null)
|
||||
{
|
||||
throw new InvalidOperationException("<EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
throw new InvalidOperationException("用户不存在");
|
||||
}
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƿ<EFBFBD><EFBFBD>ѱ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD><EFBFBD><EFBFBD>
|
||||
// 检查手机号是否已被其他用户绑定
|
||||
var existingUser = await _userService.GetUserByMobileAsync(mobile);
|
||||
|
||||
if (existingUser != null && existingUser.Id != userId)
|
||||
{
|
||||
// 5.2 <EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѱ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD>ϲ<EFBFBD><EFBFBD>˻<EFBFBD>
|
||||
// 5.2 手机号已被其他用户绑定,需要合并账户
|
||||
return await MergeAccountsAsync(currentUser, existingUser);
|
||||
}
|
||||
|
||||
// 5.4 <EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD>δ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֱ<EFBFBD>Ӹ<EFBFBD><EFBFBD>µ<EFBFBD>ǰ<EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD>
|
||||
// 5.4 手机号未被绑定,直接更新当前用户的手机号
|
||||
await _userService.UpdateUserAsync(userId, new UpdateUserDto { Mobile = mobile });
|
||||
_logger.LogInformation("Mobile bound successfully: UserId={UserId}, Mobile={Mobile}", userId, MaskMobile(mobile));
|
||||
|
||||
|
|
@ -369,43 +380,43 @@ public class AuthService : IAuthService
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD>
|
||||
/// 微信授权绑定手机号
|
||||
/// Requirements: 5.1-5.5
|
||||
/// </summary>
|
||||
public async Task<BindMobileResponse> WechatBindMobileAsync(long userId, string wechatCode)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(wechatCode))
|
||||
{
|
||||
throw new ArgumentException("<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ȩcode<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD>", nameof(wechatCode));
|
||||
throw new ArgumentException("微信授权code不能为空", nameof(wechatCode));
|
||||
}
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>API<EFBFBD><EFBFBD>ȡ<EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD>
|
||||
// 调用微信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 ?? "<EFBFBD><EFBFBD>ȡ<EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD><EFBFBD>");
|
||||
throw new InvalidOperationException(mobileResult.ErrorMessage ?? "获取手机号失败");
|
||||
}
|
||||
|
||||
var mobile = mobileResult.Mobile;
|
||||
|
||||
// <EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD>ǰ<EFBFBD>û<EFBFBD>
|
||||
// 获取当前用户
|
||||
var currentUser = await _userService.GetUserByIdAsync(userId);
|
||||
if (currentUser == null)
|
||||
{
|
||||
throw new InvalidOperationException("<EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
throw new InvalidOperationException("用户不存在");
|
||||
}
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƿ<EFBFBD><EFBFBD>ѱ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD><EFBFBD><EFBFBD>
|
||||
// 检查手机号是否已被其他用户绑定
|
||||
var existingUser = await _userService.GetUserByMobileAsync(mobile);
|
||||
|
||||
if (existingUser != null && existingUser.Id != userId)
|
||||
{
|
||||
// 5.2 <EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѱ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD>ϲ<EFBFBD><EFBFBD>˻<EFBFBD>
|
||||
// 5.2 手机号已被其他用户绑定,需要合并账户
|
||||
return await MergeAccountsAsync(currentUser, existingUser);
|
||||
}
|
||||
|
||||
// 5.4 <EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD>δ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֱ<EFBFBD>Ӹ<EFBFBD><EFBFBD>µ<EFBFBD>ǰ<EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD>
|
||||
// 5.4 手机号未被绑定,直接更新当前用户的手机号
|
||||
await _userService.UpdateUserAsync(userId, new UpdateUserDto { Mobile = mobile });
|
||||
_logger.LogInformation("Mobile bound via WeChat successfully: UserId={UserId}, Mobile={Mobile}", userId, MaskMobile(mobile));
|
||||
|
||||
|
|
@ -414,7 +425,7 @@ public class AuthService : IAuthService
|
|||
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD>¼<EFBFBD><EFBFBD>¼<EFBFBD><EFBFBD>Ϣ
|
||||
/// 记录登录信息
|
||||
/// Requirements: 6.1, 6.3, 6.4
|
||||
/// </summary>
|
||||
public async Task<RecordLoginResponse> RecordLoginAsync(long userId, string? device, string? deviceInfo)
|
||||
|
|
@ -422,17 +433,17 @@ public class AuthService : IAuthService
|
|||
var user = await _userService.GetUserByIdAsync(userId);
|
||||
if (user == null)
|
||||
{
|
||||
throw new InvalidOperationException("<EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
throw new InvalidOperationException("用户不存在");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// <EFBFBD><EFBFBD>ȡ<EFBFBD>ͻ<EFBFBD><EFBFBD><EFBFBD>IP<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʹ<EFBFBD>ÿ<EFBFBD><EFBFBD>ַ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊռλ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʵ<EFBFBD><EFBFBD>IPӦ<EFBFBD><EFBFBD>Controller<EFBFBD><EFBFBD><EFBFBD>룩
|
||||
// 获取客户端IP(这里使用空字符串作为占位,实际IP应由Controller传入)
|
||||
var clientIp = deviceInfo ?? string.Empty;
|
||||
|
||||
var now = DateTime.Now;
|
||||
|
||||
// 6.1 <EFBFBD><EFBFBD>¼<EFBFBD><EFBFBD>¼<EFBFBD><EFBFBD>־
|
||||
// 6.1 记录登录日志
|
||||
var loginLog = new UserLoginLog
|
||||
{
|
||||
UserId = userId,
|
||||
|
|
@ -446,7 +457,7 @@ public class AuthService : IAuthService
|
|||
|
||||
await _dbContext.UserLoginLogs.AddAsync(loginLog);
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>¼ʱ<EFBFBD><EFBFBD>
|
||||
// 更新用户最后登录时间
|
||||
user.LastLoginTime = now;
|
||||
user.LastLoginIp = clientIp;
|
||||
_dbContext.Users.Update(user);
|
||||
|
|
@ -455,7 +466,7 @@ public class AuthService : IAuthService
|
|||
|
||||
_logger.LogInformation("Login recorded: UserId={UserId}, Device={Device}, IP={IP}", userId, device, clientIp);
|
||||
|
||||
// 6.4 <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD><EFBFBD><EFBFBD>uid<EFBFBD><EFBFBD><EFBFBD>dzƺ<EFBFBD>ͷ<EFBFBD><EFBFBD>
|
||||
// 6.4 返回用户的uid、昵称和头像
|
||||
return new RecordLoginResponse
|
||||
{
|
||||
Uid = user.Uid,
|
||||
|
|
@ -471,33 +482,33 @@ public class AuthService : IAuthService
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// H5<EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD>ţ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD>룩
|
||||
/// H5绑定手机号(无需验证码)
|
||||
/// Requirements: 13.1
|
||||
/// </summary>
|
||||
public async Task<BindMobileResponse> BindMobileH5Async(long userId, string mobile)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(mobile))
|
||||
{
|
||||
throw new ArgumentException("<EFBFBD>ֻ<EFBFBD><EFBFBD>Ų<EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD>", nameof(mobile));
|
||||
throw new ArgumentException("手机号不能为空", nameof(mobile));
|
||||
}
|
||||
|
||||
// <EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD>ǰ<EFBFBD>û<EFBFBD>
|
||||
// 获取当前用户
|
||||
var currentUser = await _userService.GetUserByIdAsync(userId);
|
||||
if (currentUser == null)
|
||||
{
|
||||
throw new InvalidOperationException("<EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
throw new InvalidOperationException("用户不存在");
|
||||
}
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƿ<EFBFBD><EFBFBD>ѱ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD><EFBFBD><EFBFBD>
|
||||
// 检查手机号是否已被其他用户绑定
|
||||
var existingUser = await _userService.GetUserByMobileAsync(mobile);
|
||||
|
||||
if (existingUser != null && existingUser.Id != userId)
|
||||
{
|
||||
// <EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѱ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD>ϲ<EFBFBD><EFBFBD>˻<EFBFBD>
|
||||
// 手机号已被其他用户绑定,需要合并账户
|
||||
return await MergeAccountsAsync(currentUser, existingUser);
|
||||
}
|
||||
|
||||
// <EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD>δ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֱ<EFBFBD>Ӹ<EFBFBD><EFBFBD>µ<EFBFBD>ǰ<EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD>
|
||||
// 手机号未被绑定,直接更新当前用户的手机号
|
||||
await _userService.UpdateUserAsync(userId, new UpdateUserDto { Mobile = mobile });
|
||||
_logger.LogInformation("H5 Mobile bound successfully: UserId={UserId}, Mobile={Mobile}", userId, MaskMobile(mobile));
|
||||
|
||||
|
|
@ -505,7 +516,7 @@ public class AuthService : IAuthService
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD>˺<EFBFBD>ע<EFBFBD><EFBFBD>
|
||||
/// 账号注销
|
||||
/// Requirements: 7.1-7.3
|
||||
/// </summary>
|
||||
public async Task LogOffAsync(long userId, int type)
|
||||
|
|
@ -513,23 +524,23 @@ public class AuthService : IAuthService
|
|||
var user = await _userService.GetUserByIdAsync(userId);
|
||||
if (user == null)
|
||||
{
|
||||
throw new InvalidOperationException("<EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
throw new InvalidOperationException("用户不存在");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 7.1 <EFBFBD><EFBFBD>¼ע<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>־
|
||||
var action = type == 0 ? "ע<EFBFBD><EFBFBD><EFBFBD>˺<EFBFBD>" : "ȡ<><C8A1>ע<EFBFBD><D7A2>";
|
||||
// 7.1 记录注销操作日志
|
||||
var action = type == 0 ? "注销账号" : "取消注销";
|
||||
_logger.LogInformation("User log off request: UserId={UserId}, Type={Type}, Action={Action}", userId, type, action);
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ӹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ע<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>磺
|
||||
// - <EFBFBD><EFBFBD><EFBFBD>û<EFBFBD>״̬<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD>ע<EFBFBD><EFBFBD>
|
||||
// - <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD><EFBFBD><EFBFBD>صĻ<EFBFBD><EFBFBD><EFBFBD>
|
||||
// - <EFBFBD><EFBFBD><EFBFBD><EFBFBD>֪ͨ<EFBFBD><EFBFBD>
|
||||
// 这里可以添加更多的注销逻辑,比如:
|
||||
// - 将用户状态设置为已注销
|
||||
// - 清除用户相关的缓存
|
||||
// - 发送通知等
|
||||
|
||||
if (type == 0)
|
||||
{
|
||||
// ע<EFBFBD><EFBFBD><EFBFBD>˺<EFBFBD> - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD>״̬Ϊ<CCAC><CEAA><EFBFBD><EFBFBD>
|
||||
// 注销账号 - 软删除,设置用户状态为禁用
|
||||
user.Status = 0;
|
||||
_dbContext.Users.Update(user);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
|
@ -537,14 +548,14 @@ public class AuthService : IAuthService
|
|||
}
|
||||
else if (type == 1)
|
||||
{
|
||||
// ȡ<EFBFBD><EFBFBD>ע<EFBFBD><EFBFBD> - <20>ָ<EFBFBD><D6B8>û<EFBFBD>״̬
|
||||
// 取消注销 - 恢复用户状态
|
||||
user.Status = 1;
|
||||
_dbContext.Users.Update(user);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
_logger.LogInformation("User account reactivated: UserId={UserId}", userId);
|
||||
}
|
||||
|
||||
// 7.2 <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ע<EFBFBD><EFBFBD><EFBFBD>ɹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϣ<EFBFBD><EFBFBD>ͨ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>׳<EFBFBD><EFBFBD>쳣<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD>ɹ<EFBFBD><EFBFBD><EFBFBD>
|
||||
// 7.2 返回注销成功的信息(通过不抛出异常来表示成功)
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -557,24 +568,24 @@ public class AuthService : IAuthService
|
|||
#region Refresh Token Methods
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD><EFBFBD><EFBFBD> Refresh Token <20><><EFBFBD>洢<EFBFBD><E6B4A2><EFBFBD><EFBFBD><EFBFBD>ݿ<EFBFBD>
|
||||
/// 生成 Refresh Token 并存储到数据库
|
||||
/// Requirements: 1.4, 1.5, 4.1
|
||||
/// </summary>
|
||||
/// <param name="userId"><EFBFBD>û<EFBFBD>ID</param>
|
||||
/// <param name="ipAddress"><EFBFBD>ͻ<EFBFBD><EFBFBD><EFBFBD> IP <20><>ַ</param>
|
||||
/// <returns><EFBFBD><EFBFBD><EFBFBD>ɵ<EFBFBD> Refresh Token <20><><EFBFBD><EFBFBD></returns>
|
||||
/// <param name="userId">用户ID</param>
|
||||
/// <param name="ipAddress">客户端 IP 地址</param>
|
||||
/// <returns>生成的 Refresh Token 字符串</returns>
|
||||
private async Task<string> GenerateRefreshTokenAsync(long userId, string? ipAddress)
|
||||
{
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Refresh Token
|
||||
// 生成随机 Refresh Token
|
||||
var refreshToken = GenerateSecureRandomString(RefreshTokenLength);
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD> SHA256 <20><>ϣֵ<CFA3><D6B5><EFBFBD>ڴ洢
|
||||
// 计算 SHA256 哈希值用于存储
|
||||
var tokenHash = ComputeSha256Hash(refreshToken);
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD>䣨7<EFBFBD>죩
|
||||
// 设置过期时间(7天)
|
||||
var expiresAt = DateTime.Now.AddDays(_jwtSettings.RefreshTokenExpirationDays);
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݿ<EFBFBD><EFBFBD>¼
|
||||
// 保存数据库记录
|
||||
var userRefreshToken = new UserRefreshToken
|
||||
{
|
||||
UserId = userId,
|
||||
|
|
@ -593,21 +604,21 @@ public class AuthService : IAuthService
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD><EFBFBD>ɵ<EFBFBD>¼<EFBFBD><EFBFBD>Ӧ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˫ Token<65><6E>
|
||||
/// 生成登录响应(包含双 Token)
|
||||
/// Requirements: 1.1, 1.2, 1.3, 1.4, 1.5
|
||||
/// </summary>
|
||||
/// <param name="user"><EFBFBD>û<EFBFBD>ʵ<EFBFBD><EFBFBD></param>
|
||||
/// <param name="ipAddress"><EFBFBD>ͻ<EFBFBD><EFBFBD><EFBFBD> IP <20><>ַ</param>
|
||||
/// <returns><EFBFBD><EFBFBD>¼<EFBFBD><EFBFBD>Ӧ</returns>
|
||||
/// <param name="user">用户实体</param>
|
||||
/// <param name="ipAddress">客户端 IP 地址</param>
|
||||
/// <returns>登录响应</returns>
|
||||
private async Task<LoginResponse> GenerateLoginResponseAsync(User user, string? ipAddress)
|
||||
{
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD> Access Token (JWT)
|
||||
// 生成 Access Token (JWT)
|
||||
var accessToken = _jwtService.GenerateToken(user);
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD> Refresh Token <20><><EFBFBD>洢
|
||||
// 生成 Refresh Token 并存储
|
||||
var refreshToken = await GenerateRefreshTokenAsync(user.Id, ipAddress);
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD> Access Token <20><><EFBFBD><EFBFBD>ʱ<EFBFBD>䣨<EFBFBD>룩
|
||||
// 计算 Access Token 过期时间(秒)
|
||||
var expiresIn = _jwtSettings.ExpirationMinutes * 60;
|
||||
|
||||
return new LoginResponse
|
||||
|
|
@ -620,7 +631,7 @@ public class AuthService : IAuthService
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// ˢ<EFBFBD><EFBFBD> Token
|
||||
/// 刷新 Token
|
||||
/// Requirements: 2.1-2.6
|
||||
/// </summary>
|
||||
public async Task<RefreshTokenResult> RefreshTokenAsync(string refreshToken, string? ipAddress)
|
||||
|
|
@ -628,15 +639,15 @@ public class AuthService : IAuthService
|
|||
if (string.IsNullOrWhiteSpace(refreshToken))
|
||||
{
|
||||
_logger.LogWarning("Refresh token is empty");
|
||||
return RefreshTokenResult.Fail("ˢ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ʋ<EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD>");
|
||||
return RefreshTokenResult.Fail("刷新令牌不能为空");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD> Token <20><>ϣֵ
|
||||
// 计算 Token 哈希值
|
||||
var tokenHash = ComputeSha256Hash(refreshToken);
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD> Token <20><>¼
|
||||
// 查找 Token 记录
|
||||
var storedToken = await _dbContext.UserRefreshTokens
|
||||
.Include(t => t.User)
|
||||
.FirstOrDefaultAsync(t => t.TokenHash == tokenHash);
|
||||
|
|
@ -644,47 +655,47 @@ public class AuthService : IAuthService
|
|||
if (storedToken == null)
|
||||
{
|
||||
_logger.LogWarning("Refresh token not found: {TokenHash}", tokenHash.Substring(0, 8) + "...");
|
||||
return RefreshTokenResult.Fail("<EFBFBD><EFBFBD>Ч<EFBFBD><EFBFBD>ˢ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
return RefreshTokenResult.Fail("无效的刷新令牌");
|
||||
}
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƿ<EFBFBD><EFBFBD>ѹ<EFBFBD><EFBFBD><EFBFBD>
|
||||
// 检查是否已过期
|
||||
if (storedToken.IsExpired)
|
||||
{
|
||||
_logger.LogWarning("Refresh token expired for user {UserId}", storedToken.UserId);
|
||||
return RefreshTokenResult.Fail("ˢ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѹ<EFBFBD><EFBFBD><EFBFBD>");
|
||||
return RefreshTokenResult.Fail("刷新令牌已过期");
|
||||
}
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƿ<EFBFBD><EFBFBD>ѳ<EFBFBD><EFBFBD><EFBFBD>
|
||||
// 检查是否已撤销
|
||||
if (storedToken.IsRevoked)
|
||||
{
|
||||
_logger.LogWarning("Refresh token revoked for user {UserId}", storedToken.UserId);
|
||||
return RefreshTokenResult.Fail("ˢ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧЧ");
|
||||
return RefreshTokenResult.Fail("刷新令牌已失效");
|
||||
}
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD><EFBFBD>Ƿ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ч
|
||||
// 检查用户是否存在且有效
|
||||
var user = storedToken.User;
|
||||
if (user == null)
|
||||
{
|
||||
_logger.LogWarning("User not found for refresh token");
|
||||
return RefreshTokenResult.Fail("<EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
return RefreshTokenResult.Fail("用户不存在");
|
||||
}
|
||||
|
||||
if (user.Status == 0)
|
||||
{
|
||||
_logger.LogWarning("User {UserId} is disabled", user.Id);
|
||||
return RefreshTokenResult.Fail("<EFBFBD>˺<EFBFBD><EFBFBD>ѱ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
return RefreshTokenResult.Fail("账号已被禁用");
|
||||
}
|
||||
|
||||
// Token <EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>µ<EFBFBD> Refresh Token
|
||||
// Token 轮换:生成新的 Refresh Token
|
||||
var newRefreshToken = GenerateSecureRandomString(RefreshTokenLength);
|
||||
var newTokenHash = ComputeSha256Hash(newRefreshToken);
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Token <20><><EFBFBD><EFBFBD>¼<EFBFBD><C2BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϵ
|
||||
// 标记旧 Token 的记录和替换关系
|
||||
storedToken.RevokedAt = DateTime.Now;
|
||||
storedToken.RevokedByIp = ipAddress;
|
||||
storedToken.ReplacedByToken = newTokenHash;
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>µ<EFBFBD> Token <20><>¼
|
||||
// 保存新的 Token 记录
|
||||
var newUserRefreshToken = new UserRefreshToken
|
||||
{
|
||||
UserId = user.Id,
|
||||
|
|
@ -697,7 +708,7 @@ public class AuthService : IAuthService
|
|||
await _dbContext.UserRefreshTokens.AddAsync(newUserRefreshToken);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>µ<EFBFBD> Access Token
|
||||
// 生成新的 Access Token
|
||||
var accessToken = _jwtService.GenerateToken(user);
|
||||
var expiresIn = _jwtSettings.ExpirationMinutes * 60;
|
||||
|
||||
|
|
@ -714,12 +725,12 @@ public class AuthService : IAuthService
|
|||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error refreshing token");
|
||||
return RefreshTokenResult.Fail("ˢ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD>ܣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ժ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
return RefreshTokenResult.Fail("刷新令牌失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD><EFBFBD><EFBFBD> Token
|
||||
/// 撤销 Token
|
||||
/// Requirements: 4.4
|
||||
/// </summary>
|
||||
public async Task RevokeTokenAsync(string refreshToken, string? ipAddress)
|
||||
|
|
@ -732,10 +743,10 @@ public class AuthService : IAuthService
|
|||
|
||||
try
|
||||
{
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD> Token <20><>ϣֵ
|
||||
// 计算 Token 哈希值
|
||||
var tokenHash = ComputeSha256Hash(refreshToken);
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD> Token <20><>¼
|
||||
// 查找 Token 记录
|
||||
var storedToken = await _dbContext.UserRefreshTokens
|
||||
.FirstOrDefaultAsync(t => t.TokenHash == tokenHash);
|
||||
|
||||
|
|
@ -745,14 +756,14 @@ public class AuthService : IAuthService
|
|||
return;
|
||||
}
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ѿ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֱ<EFBFBD>ӷ<EFBFBD><EFBFBD><EFBFBD>
|
||||
// 如果已经撤销,直接返回
|
||||
if (storedToken.IsRevoked)
|
||||
{
|
||||
_logger.LogInformation("Refresh token already revoked");
|
||||
return;
|
||||
}
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD> Token
|
||||
// 撤销 Token
|
||||
storedToken.RevokedAt = DateTime.Now;
|
||||
storedToken.RevokedByIp = ipAddress;
|
||||
|
||||
|
|
@ -768,14 +779,14 @@ public class AuthService : IAuthService
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Token
|
||||
/// 撤销用户所有的 Token
|
||||
/// Requirements: 4.4
|
||||
/// </summary>
|
||||
public async Task RevokeAllUserTokensAsync(long userId, string? ipAddress)
|
||||
{
|
||||
try
|
||||
{
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ч<EFBFBD><EFBFBD> Token
|
||||
// 查找用户所有有效的 Token
|
||||
var activeTokens = await _dbContext.UserRefreshTokens
|
||||
.Where(t => t.UserId == userId && t.RevokedAt == null)
|
||||
.ToListAsync();
|
||||
|
|
@ -809,7 +820,7 @@ public class AuthService : IAuthService
|
|||
#region Private Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD>ϲ<EFBFBD><EFBFBD>˻<EFBFBD> - <20><><EFBFBD><EFBFBD>ǰ<EFBFBD>û<EFBFBD><C3BB><EFBFBD>openidǨ<64>Ƶ<EFBFBD><C6B5>ֻ<EFBFBD><D6BB><EFBFBD><EFBFBD>û<EFBFBD>
|
||||
/// 合并账户 - 将当前用户的openid迁移到手机号用户
|
||||
/// </summary>
|
||||
private async Task<BindMobileResponse> MergeAccountsAsync(User currentUser, User mobileUser)
|
||||
{
|
||||
|
|
@ -819,7 +830,7 @@ public class AuthService : IAuthService
|
|||
_logger.LogInformation("Merging accounts: CurrentUserId={CurrentUserId}, MobileUserId={MobileUserId}",
|
||||
currentUser.Id, mobileUser.Id);
|
||||
|
||||
// 5.2 <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǰ<EFBFBD>û<EFBFBD><EFBFBD><EFBFBD>openidǨ<EFBFBD>Ƶ<EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD>
|
||||
// 5.2 将当前用户的openid迁移到手机号用户
|
||||
if (!string.IsNullOrWhiteSpace(currentUser.OpenId))
|
||||
{
|
||||
mobileUser.OpenId = currentUser.OpenId;
|
||||
|
|
@ -831,13 +842,13 @@ public class AuthService : IAuthService
|
|||
mobileUser.UpdateTime = DateTime.Now;
|
||||
_dbContext.Users.Update(mobileUser);
|
||||
|
||||
// ɾ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǰ<EFBFBD>û<EFBFBD>
|
||||
// 删除当前用户
|
||||
_dbContext.Users.Remove(currentUser);
|
||||
|
||||
await _dbContext.SaveChangesAsync();
|
||||
await transaction.CommitAsync();
|
||||
|
||||
// 5.3 <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>µ<EFBFBD>token
|
||||
// 5.3 生成新的token
|
||||
var newToken = _jwtService.GenerateToken(mobileUser);
|
||||
|
||||
_logger.LogInformation("Accounts merged successfully: NewUserId={NewUserId}", mobileUser.Id);
|
||||
|
|
@ -906,18 +917,18 @@ public class AuthService : IAuthService
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĭ<EFBFBD><EFBFBD>ͷ<EFBFBD><EFBFBD>URL
|
||||
/// 生成默认头像URL
|
||||
/// </summary>
|
||||
private static string GenerateDefaultAvatar(string seed)
|
||||
{
|
||||
// ʹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĭ<EFBFBD><EFBFBD>ͷ<EFBFBD><EFBFBD>URL
|
||||
// ʵ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD>п<EFBFBD><EFBFBD><EFBFBD>ʹ<EFBFBD><EFBFBD>Identicon<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͷ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɷ<EFBFBD><EFBFBD><EFBFBD>
|
||||
// 使用随机数生成一个简单的默认头像URL
|
||||
// 实际项目中可以使用Identicon或其他头像生成方案
|
||||
var hash = ComputeMd5(seed);
|
||||
return $"https://api.dicebear.com/7.x/identicon/svg?seed={hash}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>MD5<EFBFBD><EFBFBD>ϣ
|
||||
/// 计算MD5哈希
|
||||
/// </summary>
|
||||
private static string ComputeMd5(string input)
|
||||
{
|
||||
|
|
@ -927,7 +938,7 @@ public class AuthService : IAuthService
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ַ<EFBFBD><EFBFBD><EFBFBD>
|
||||
/// 生成随机字符串
|
||||
/// </summary>
|
||||
private static string GenerateRandomString(int length)
|
||||
{
|
||||
|
|
@ -941,7 +952,7 @@ public class AuthService : IAuthService
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD>
|
||||
/// 手机号脱敏
|
||||
/// </summary>
|
||||
private static string MaskMobile(string mobile)
|
||||
{
|
||||
|
|
@ -952,7 +963,7 @@ public class AuthService : IAuthService
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>е<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
/// 获取年份中的周数
|
||||
/// </summary>
|
||||
private static int GetWeekOfYear(DateTime date)
|
||||
{
|
||||
|
|
@ -961,7 +972,7 @@ public class AuthService : IAuthService
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD><EFBFBD><EFBFBD> SHA256 <20><>ϣֵ
|
||||
/// 计算 SHA256 哈希值
|
||||
/// Requirements: 4.1
|
||||
/// </summary>
|
||||
private static string ComputeSha256Hash(string input)
|
||||
|
|
@ -972,7 +983,7 @@ public class AuthService : IAuthService
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD><EFBFBD>ɰ<EFBFBD>ȫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ַ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Refresh Token<65><6E>
|
||||
/// 生成安全随机字符串(用于 Refresh Token)
|
||||
/// </summary>
|
||||
private static string GenerateSecureRandomString(int length)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user