@ -1,5 +1,6 @@
using System.Security.Cryptography ;
using System.Text ;
using System.Text.Json ;
using MiAssessment.Core.Interfaces ;
using MiAssessment.Model.Data ;
using MiAssessment.Model.Entities ;
@ -10,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
{
@ -20,6 +21,7 @@ public class AuthService : IAuthService
private readonly IWechatService _wechatService ;
private readonly IIpLocationService _ipLocationService ;
private readonly IRedisService _redisService ;
private readonly IConfigService _configService ;
private readonly JwtSettings _jwtSettings ;
private readonly ILogger < AuthService > _logger ;
@ -28,7 +30,7 @@ public class AuthService : IAuthService
private const string SmsCodeKeyPrefix = "sms:code:" ;
private const int DebounceSeconds = 3 ;
// Refresh Token 配置
// Refresh Token <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD>
private const int RefreshTokenLength = 64 ;
public AuthService (
@ -38,6 +40,7 @@ public class AuthService : IAuthService
IWechatService wechatService ,
IIpLocationService ipLocationService ,
IRedisService redisService ,
IConfigService configService ,
JwtSettings jwtSettings ,
ILogger < AuthService > logger )
{
@ -47,50 +50,51 @@ public class AuthService : IAuthService
_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 ) ) ;
}
/// <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 )
{
_logger . LogInformation ( "[AuthService] 微信登录开始, code={Code}, pid={Pid}", code , pid ) ;
_logger . LogInformation ( "[AuthService] <EFBFBD> ŵ<EFBFBD> ¼<EFBFBD> <EFBFBD> ʼ <EFBFBD> <EFBFBD> code={Code}, pid={Pid}", code , pid ) ;
if ( string . IsNullOrWhiteSpace ( code ) )
{
_logger . LogWarning ( "[AuthService] 微信登录失败: code为空 ") ;
_logger . LogWarning ( "[AuthService] <EFBFBD> ŵ<EFBFBD> ¼ʧ<EFBFBD> ܣ<EFBFBD> codeΪ<EFBFBD> <EFBFBD> ") ;
return new LoginResult
{
Success = false ,
ErrorMessage = " 授权code不能为空 "
ErrorMessage = " <EFBFBD> <EFBFBD> Ȩcode<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ϊ<EFBFBD> <EFBFBD> "
} ;
}
try
{
// 1.6 防抖机制 - 3秒内不允许重复登录
// 1.6 <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> - 3<> <33> <EFBFBD> ڲ<EFBFBD> <DAB2> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ظ<EFBFBD> <D8B8> <EFBFBD> ¼
var debounceKey = $"{LoginDebounceKeyPrefix}wechat:{code}" ;
_logger . LogInformation ( "[AuthService] 检查防抖锁 : {Key}", debounceKey ) ;
_logger . LogInformation ( "[AuthService] <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> : {Key}", debounceKey ) ;
var lockAcquired = await _redisService . TryAcquireLockAsync ( debounceKey , "1" , TimeSpan . FromSeconds ( DebounceSeconds ) ) ;
if ( ! lockAcquired )
{
_logger . LogWarning ( "[AuthService] 防抖触发,拒绝重复登录请求 : {Code}", code ) ;
_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 ) ;
return new LoginResult
{
Success = false ,
ErrorMessage = " 请勿频繁登录 "
ErrorMessage = " <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ƶ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ¼ "
} ;
}
_logger . LogInformation ( "[AuthService] 防抖锁获取成功 ") ;
_logger . LogInformation ( "[AuthService] <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ȡ<EFBFBD> ɹ<EFBFBD> ") ;
// 1.1 调用微信API获取openid和 unionid
_logger . LogInformation ( "[AuthService] 开始调用微信API获取 openid...") ;
// 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...") ;
var wechatResult = await _wechatService . GetOpenIdAsync ( code ) ;
_logger . LogInformation ( "[AuthService] 微信API调用完成, Success={Success}, OpenId={OpenId}, UnionId={UnionId}, Error={Error}",
_logger . LogInformation ( "[AuthService] <EFBFBD> <EFBFBD> API<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ɣ <EFBFBD> Success={Success}, OpenId={OpenId}, UnionId={UnionId}, Error={Error}",
wechatResult . Success ,
wechatResult . OpenId ? ? "null" ,
wechatResult . UnionId ? ? "null" ,
@ -98,89 +102,89 @@ public class AuthService : IAuthService
if ( ! wechatResult . Success )
{
_logger . LogWarning ( "[AuthService] 微信API调用失败 : {Error}", wechatResult . ErrorMessage ) ;
_logger . LogWarning ( "[AuthService] <EFBFBD> <EFBFBD> API<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ʧ<EFBFBD> <EFBFBD> : {Error}", wechatResult . ErrorMessage ) ;
return new LoginResult
{
Success = false ,
ErrorMessage = wechatResult . ErrorMessage ? ? " 登录失败,请稍后重试 "
ErrorMessage = wechatResult . ErrorMessage ? ? " <EFBFBD> <EFBFBD> ¼ʧ<EFBFBD> ܣ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ժ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> "
} ;
}
var openId = wechatResult . OpenId ! ;
var unionId = wechatResult . UnionId ;
// 1.2 查找用户 - 优先通过unionid查找, 其次通过openid查找
// 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>
User ? user = null ;
if ( ! string . IsNullOrWhiteSpace ( unionId ) )
{
_logger . LogInformation ( "[AuthService] 尝试通过unionid查找用户 : {UnionId}", unionId ) ;
_logger . LogInformation ( "[AuthService] <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ͨ<EFBFBD> <EFBFBD> unionid<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> û<EFBFBD> : {UnionId}", unionId ) ;
user = await _userService . GetUserByUnionIdAsync ( unionId ) ;
_logger . LogInformation ( "[AuthService] unionid 查找结果: {Found}", user ! = null ? $"找到用户ID={user.Id}" : "未找到 ") ;
_logger . LogInformation ( "[AuthService] unionid <EFBFBD> <EFBFBD> <EFBFBD> ҽ <EFBFBD> <EFBFBD> : {Found}", user ! = null ? $"<22> ҵ<EFBFBD> <D2B5> û<EFBFBD> ID={user.Id}" : "δ<> ҵ<EFBFBD> ") ;
}
if ( user = = null )
{
_logger . LogInformation ( "[AuthService] 尝试通过openid查找用户 : {OpenId}", openId ) ;
_logger . LogInformation ( "[AuthService] <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ͨ<EFBFBD> <EFBFBD> openid<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> û<EFBFBD> : {OpenId}", openId ) ;
user = await _userService . GetUserByOpenIdAsync ( openId ) ;
_logger . LogInformation ( "[AuthService] openid 查找结果: {Found}", user ! = null ? $"找到用户ID={user.Id}" : "未找到 ") ;
_logger . LogInformation ( "[AuthService] openid <EFBFBD> <EFBFBD> <EFBFBD> ҽ <EFBFBD> <EFBFBD> : {Found}", user ! = null ? $"<22> ҵ<EFBFBD> <D2B5> û<EFBFBD> ID={user.Id}" : "δ<> ҵ<EFBFBD> ") ;
}
if ( user = = null )
{
// 1.3 用户不存在,创建新用户
_logger . LogInformation ( "[AuthService] 用户不存在,开始创建新用户 ...") ;
// 1.3 <EFBFBD> û<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ڣ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> û<EFBFBD>
_logger . LogInformation ( "[AuthService] <EFBFBD> û<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ڣ<EFBFBD> <EFBFBD> <EFBFBD> ʼ <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> û<EFBFBD> ...") ;
var createDto = new CreateUserDto
{
OpenId = openId ,
UnionId = unionId ,
Nickname = $"用户{Random.Shared.Next(100000, 999999)}" ,
Headimg = Ge nera te DefaultAvatar( openId ) ,
Nickname = await GetDefaultNicknameAsync ( ) ,
Headimg = await Ge tDefaultAvatarAsync ( openId ) ,
Pid = pid ? ? 0
} ;
user = await _userService . CreateUserAsync ( createDto ) ;
_logger . LogInformation ( "[AuthService] 新用户创建成功 : UserId={UserId}, OpenId={OpenId}", user . Id , openId ) ;
_logger . LogInformation ( "[AuthService] <EFBFBD> <EFBFBD> <EFBFBD> û<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ɹ<EFBFBD> : UserId={UserId}, OpenId={OpenId}", user . Id , openId ) ;
}
else
{
// 1.4 用户存在, 更新unionid( 如果之前为空)
// 1.4 <EFBFBD> û<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ڣ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> unionid<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ֮ǰΪ<EFBFBD> գ <EFBFBD>
if ( string . IsNullOrWhiteSpace ( user . UnionId ) & & ! string . IsNullOrWhiteSpace ( unionId ) )
{
_logger . LogInformation ( "[AuthService] 更新用户 unionid: UserId={UserId}", user . Id ) ;
_logger . LogInformation ( "[AuthService] <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> û<EFBFBD> unionid: UserId={UserId}", user . Id ) ;
await _userService . UpdateUserAsync ( user . Id , new UpdateUserDto { UnionId = unionId } ) ;
_logger . LogInformation ( "[AuthService] unionid 更新成功 ") ;
_logger . LogInformation ( "[AuthService] unionid <EFBFBD> <EFBFBD> <EFBFBD> ³ɹ<EFBFBD> ") ;
}
}
// 1.5 生成双 Token( Access Token + Refresh Token)
_logger . LogInformation ( "[AuthService] 开始生成双 Token: UserId={UserId}", user . Id ) ;
// 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 ) ;
var loginResponse = await GenerateLoginResponseAsync ( user , null ) ;
_logger . LogInformation ( "[AuthService] 双 Token 生成成功, AccessToken长度 ={Length}", loginResponse . AccessToken ? . Length ? ? 0 ) ;
_logger . LogInformation ( "[AuthService] ˫ Token <20> <> <EFBFBD> ɳɹ<C9B3> <C9B9> <EFBFBD> AccessToken<65> <6E> <EFBFBD> <EFBFBD> ={Length}", loginResponse . AccessToken ? . Length ? ? 0 ) ;
_logger . LogInformation ( "[AuthService] 微信登录成功 : UserId={UserId}", user . Id ) ;
_logger . LogInformation ( "[AuthService] <EFBFBD> ŵ<EFBFBD> ¼<EFBFBD> ɹ<EFBFBD> : UserId={UserId}", user . Id ) ;
return new LoginResult
{
Success = true ,
Token = loginResponse . AccessToken , // 兼容旧版
Token = loginResponse . AccessToken , // <EFBFBD> <EFBFBD> <EFBFBD> ݾɰ<EFBFBD>
UserId = user . Id ,
LoginResponse = loginResponse
} ;
}
catch ( Exception ex )
{
_logger . LogError ( ex , "[AuthService] 微信登录异常 : code={Code}, Message={Message}, StackTrace={StackTrace}",
_logger . LogError ( ex , "[AuthService] <EFBFBD> ŵ<EFBFBD> ¼<EFBFBD> 쳣 : code={Code}, Message={Message}, StackTrace={StackTrace}",
code , ex . Message , ex . StackTrace ) ;
return new LoginResult
{
Success = false ,
ErrorMessage = " 网络故障,请稍后再试 "
ErrorMessage = " <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ϣ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ժ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> "
} ;
}
}
/// <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 )
@ -190,7 +194,7 @@ public class AuthService : IAuthService
return new LoginResult
{
Success = false ,
ErrorMessage = " 手机号不能为空 "
ErrorMessage = " <EFBFBD> ֻ<EFBFBD> <EFBFBD> Ų<EFBFBD> <EFBFBD> <EFBFBD> Ϊ<EFBFBD> <EFBFBD> "
} ;
}
@ -199,13 +203,13 @@ public class AuthService : IAuthService
return new LoginResult
{
Success = false ,
ErrorMessage = " 验证码不能为空 "
ErrorMessage = " <EFBFBD> <EFBFBD> ֤<EFBFBD> 벻<EFBFBD> <EFBFBD> Ϊ<EFBFBD> <EFBFBD> "
} ;
}
try
{
// 2.6 防抖机制 - 3秒内不允许重复登录
// 2.6 <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> - 3<> <33> <EFBFBD> ڲ<EFBFBD> <DAB2> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ظ<EFBFBD> <D8B8> <EFBFBD> ¼
var debounceKey = $"{LoginDebounceKeyPrefix}mobile:{mobile}" ;
var lockAcquired = await _redisService . TryAcquireLockAsync ( debounceKey , "1" , TimeSpan . FromSeconds ( DebounceSeconds ) ) ;
if ( ! lockAcquired )
@ -214,11 +218,11 @@ public class AuthService : IAuthService
return new LoginResult
{
Success = false ,
ErrorMessage = " 请勿频繁登录 "
ErrorMessage = " <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ƶ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ¼ "
} ;
}
// 2.1 从Redis获取并验证验证码
// 2.1 <EFBFBD> <EFBFBD> Redis<EFBFBD> <EFBFBD> ȡ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ֤<EFBFBD> <EFBFBD> ֤<EFBFBD> <EFBFBD>
var smsCodeKey = $"{SmsCodeKeyPrefix}{mobile}" ;
var storedCode = await _redisService . GetStringAsync ( smsCodeKey ) ;
@ -228,24 +232,24 @@ public class AuthService : IAuthService
return new LoginResult
{
Success = false ,
ErrorMessage = " 验证码错误 "
ErrorMessage = " <EFBFBD> <EFBFBD> ֤<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> "
} ;
}
// 2.2 验证码验证通过后, 删除Redis中的验证码
// 2.2 <EFBFBD> <EFBFBD> ֤<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ֤ͨ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ɾ<EFBFBD> <EFBFBD> Redis<EFBFBD> е <EFBFBD> <EFBFBD> <EFBFBD> ֤<EFBFBD> <EFBFBD>
await _redisService . DeleteAsync ( smsCodeKey ) ;
// 查找用户
// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> û<EFBFBD>
var user = await _userService . GetUserByMobileAsync ( mobile ) ;
if ( user = = null )
{
// 2.3 用户不存在,创建新用户
// 2.3 <EFBFBD> û<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ڣ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> û<EFBFBD>
var createDto = new CreateUserDto
{
Mobile = mobile ,
Nickname = $"用户{Random.Shared.Next(100000, 999999)}" ,
Headimg = Ge nera te DefaultAvatar( mobile ) ,
Nickname = await GetDefaultNicknameAsync ( ) ,
Headimg = await Ge tDefaultAvatarAsync ( mobile ) ,
Pid = pid ? ? 0
} ;
@ -253,7 +257,7 @@ public class AuthService : IAuthService
_logger . LogInformation ( "New user created via mobile login: UserId={UserId}, Mobile={Mobile}" , user . Id , MaskMobile ( mobile ) ) ;
}
// 2.4 生成双 Token( Access Token + Refresh Token)
// 2.4 <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ˫ Token<65> <6E> Access Token + Refresh Token<65> <6E>
var loginResponse = await GenerateLoginResponseAsync ( user , null ) ;
_logger . LogInformation ( "Mobile login successful: UserId={UserId}" , user . Id ) ;
@ -261,7 +265,7 @@ public class AuthService : IAuthService
return new LoginResult
{
Success = true ,
Token = loginResponse . AccessToken , // 兼容旧版
Token = loginResponse . AccessToken , // <EFBFBD> <EFBFBD> <EFBFBD> ݾɰ<EFBFBD>
UserId = user . Id ,
LoginResponse = loginResponse
} ;
@ -272,58 +276,58 @@ public class AuthService : IAuthService
return new LoginResult
{
Success = false ,
ErrorMessage = " 网络故障,请稍后再试 "
ErrorMessage = " <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ϣ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ժ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> "
} ;
}
}
/// <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 ( " 手机号不能为空 ", nameof ( mobile ) ) ;
throw new ArgumentException ( " <EFBFBD> ֻ<EFBFBD> <EFBFBD> Ų<EFBFBD> <EFBFBD> <EFBFBD> Ϊ<EFBFBD> <EFBFBD> ", nameof ( mobile ) ) ;
}
if ( string . IsNullOrWhiteSpace ( code ) )
{
throw new ArgumentException ( " 验证码不能为空 ", nameof ( code ) ) ;
throw new ArgumentException ( " <EFBFBD> <EFBFBD> ֤<EFBFBD> 벻<EFBFBD> <EFBFBD> Ϊ<EFBFBD> <EFBFBD> ", nameof ( code ) ) ;
}
// 5.1 验证短信验证码
// 5.1 <EFBFBD> <EFBFBD> ֤<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ֤<EFBFBD> <EFBFBD>
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 ( " 验证码错误 ") ;
throw new InvalidOperationException ( " <EFBFBD> <EFBFBD> ֤<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ") ;
}
// 验证码验证通过后删除
// <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 ( " 用户不存在 ") ;
throw new InvalidOperationException ( " <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>
var existingUser = await _userService . GetUserByMobileAsync ( mobile ) ;
if ( existingUser ! = null & & existingUser . Id ! = userId )
{
// 5.2 手机号已被其他用户绑定,需要合并账户
// 5.2 <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 ) ;
}
// 5.4 手机号未被绑定,直接更新当前用户的手机号
// 5.4 <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 ( "Mobile bound successfully: UserId={UserId}, Mobile={Mobile}" , userId , MaskMobile ( mobile ) ) ;
@ -331,43 +335,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 ( " 微信授权code不能为空 ", nameof ( wechatCode ) ) ;
throw new ArgumentException ( " <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ȩcode<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ϊ<EFBFBD> <EFBFBD> ", nameof ( wechatCode ) ) ;
}
// 调用微信API获取手机号
// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> API<EFBFBD> <EFBFBD> ȡ<EFBFBD> ֻ<EFBFBD> <EFBFBD> <EFBFBD>
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 ? ? " 获取手机号失败 ") ;
throw new InvalidOperationException ( mobileResult . ErrorMessage ? ? " <EFBFBD> <EFBFBD> ȡ<EFBFBD> ֻ<EFBFBD> <EFBFBD> <EFBFBD> ʧ<EFBFBD> <EFBFBD> ") ;
}
var mobile = mobileResult . Mobile ;
// 获取当前用户
// <EFBFBD> <EFBFBD> ȡ<EFBFBD> <EFBFBD> ǰ<EFBFBD> û<EFBFBD>
var currentUser = await _userService . GetUserByIdAsync ( userId ) ;
if ( currentUser = = null )
{
throw new InvalidOperationException ( " 用户不存在 ") ;
throw new InvalidOperationException ( " <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>
var existingUser = await _userService . GetUserByMobileAsync ( mobile ) ;
if ( existingUser ! = null & & existingUser . Id ! = userId )
{
// 5.2 手机号已被其他用户绑定,需要合并账户
// 5.2 <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 ) ;
}
// 5.4 手机号未被绑定,直接更新当前用户的手机号
// 5.4 <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 ( "Mobile bound via WeChat successfully: UserId={UserId}, Mobile={Mobile}" , userId , MaskMobile ( mobile ) ) ;
@ -376,7 +380,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 )
@ -384,17 +388,17 @@ public class AuthService : IAuthService
var user = await _userService . GetUserByIdAsync ( userId ) ;
if ( user = = null )
{
throw new InvalidOperationException ( " 用户不存在 ") ;
throw new InvalidOperationException ( " <EFBFBD> û<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ") ;
}
try
{
// 获取客户端IP( 这里使用空字符串作为占位符, 实际IP应从Controller传入)
// <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> 룩
var clientIp = deviceInfo ? ? string . Empty ;
var now = DateTime . Now ;
// 6.1 记录登录日志
// 6.1 <EFBFBD> <EFBFBD> ¼<EFBFBD> <EFBFBD> ¼<EFBFBD> <EFBFBD> ־
var loginLog = new UserLoginLog
{
UserId = userId ,
@ -408,7 +412,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 ) ;
@ -417,7 +421,7 @@ public class AuthService : IAuthService
_logger . LogInformation ( "Login recorded: UserId={UserId}, Device={Device}, IP={IP}" , userId , device , clientIp ) ;
// 6.4 返回用户的uid、昵称和头像
// 6.4 <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> û<EFBFBD> <EFBFBD> <EFBFBD> uid<EFBFBD> <EFBFBD> <EFBFBD> dzƺ<EFBFBD> ͷ<EFBFBD> <EFBFBD>
return new RecordLoginResponse
{
Uid = user . Uid ,
@ -433,33 +437,33 @@ public class AuthService : IAuthService
}
/// <summary>
/// H5 绑定手机号(无需验证码)
/// H5 <EFBFBD> <EFBFBD> <EFBFBD> ֻ<EFBFBD> <EFBFBD> ţ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ֤<EFBFBD> 룩
/// Requirements: 13.1
/// </summary>
public async Task < BindMobileResponse > BindMobileH5Async ( long userId , string mobile )
{
if ( string . IsNullOrWhiteSpace ( mobile ) )
{
throw new ArgumentException ( " 手机号不能为空 ", nameof ( mobile ) ) ;
throw new ArgumentException ( " <EFBFBD> ֻ<EFBFBD> <EFBFBD> Ų<EFBFBD> <EFBFBD> <EFBFBD> Ϊ<EFBFBD> <EFBFBD> ", nameof ( mobile ) ) ;
}
// 获取当前用户
// <EFBFBD> <EFBFBD> ȡ<EFBFBD> <EFBFBD> ǰ<EFBFBD> û<EFBFBD>
var currentUser = await _userService . GetUserByIdAsync ( userId ) ;
if ( currentUser = = null )
{
throw new InvalidOperationException ( " 用户不存在 ") ;
throw new InvalidOperationException ( " <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>
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 ) ) ;
@ -467,7 +471,7 @@ public class AuthService : IAuthService
}
/// <summary>
/// 账号注销
/// <EFBFBD> ˺<EFBFBD> ע<EFBFBD> <EFBFBD>
/// Requirements: 7.1-7.3
/// </summary>
public async Task LogOffAsync ( long userId , int type )
@ -475,23 +479,23 @@ public class AuthService : IAuthService
var user = await _userService . GetUserByIdAsync ( userId ) ;
if ( user = = null )
{
throw new InvalidOperationException ( " 用户不存在 ") ;
throw new InvalidOperationException ( " <EFBFBD> û<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ") ;
}
try
{
// 7.1 记录注销请求日志
var action = type = = 0 ? " 注销账号" : "取消注销 ";
// 7.1 <EFBFBD> <EFBFBD> ¼ע<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ־
var action = type = = 0 ? " ע<EFBFBD> <EFBFBD> <EFBFBD> ˺<EFBFBD> " : "ȡ<> <C8A1> ע<EFBFBD> <D7A2> ";
_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 ( ) ;
@ -499,14 +503,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 返回注销成功的消息(通过不抛出异常来表示成功)
// 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>
}
catch ( Exception ex )
{
@ -519,24 +523,24 @@ public class AuthService : IAuthService
#region Refresh Token Methods
/// <summary>
/// 生成 Refresh Token 并存储到数据库
/// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Refresh Token <20> <> <EFBFBD> 洢<EFBFBD> <E6B4A2> <EFBFBD> <EFBFBD> <EFBFBD> ݿ<EFBFBD>
/// Requirements: 1.4, 1.5, 4.1
/// </summary>
/// <param name="userId"> 用户 ID</param>
/// <param name="ipAddress"> 客户端 IP 地址 </param>
/// <returns> 生成的 Refresh Token 明文 </returns>
/// <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>
private async Task < string > GenerateRefreshTokenAsync ( long userId , string? ipAddress )
{
// 生成随机 Refresh Token
// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Refresh Token
var refreshToken = GenerateSecureRandomString ( RefreshTokenLength ) ;
// 计算 SHA256 哈希值用于存储
// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> SHA256 <20> <> ϣֵ<CFA3> <D6B5> <EFBFBD> ڴ洢
var tokenHash = ComputeSha256Hash ( refreshToken ) ;
// 计算过期时间( 7天)
// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ʱ<EFBFBD> 䣨7<EFBFBD> 죩
var expiresAt = DateTime . Now . AddDays ( _jwtSettings . RefreshTokenExpirationDays ) ;
// 创建数据库记录
// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ݿ<EFBFBD> <EFBFBD> ¼
var userRefreshToken = new UserRefreshToken
{
UserId = userId ,
@ -555,21 +559,21 @@ public class AuthService : IAuthService
}
/// <summary>
/// 生成登录响应(包含双 Token)
/// <EFBFBD> <EFBFBD> <EFBFBD> ɵ<EFBFBD> ¼<EFBFBD> <EFBFBD> Ӧ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ˫ Token<65> <6E>
/// Requirements: 1.1, 1.2, 1.3, 1.4, 1.5
/// </summary>
/// <param name="user"> 用户实体 </param>
/// <param name="ipAddress"> 客户端 IP 地址 </param>
/// <returns> 登录响应 </returns>
/// <param name="user"> <EFBFBD> û<EFBFBD> ʵ<EFBFBD> <EFBFBD> </param>
/// <param name="ipAddress"> <EFBFBD> ͻ<EFBFBD> <EFBFBD> <EFBFBD> IP <20> <> ַ </param>
/// <returns> <EFBFBD> <EFBFBD> ¼<EFBFBD> <EFBFBD> Ӧ </returns>
private async Task < LoginResponse > GenerateLoginResponseAsync ( User user , string? ipAddress )
{
// 生成 Access Token (JWT)
// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Access Token (JWT)
var accessToken = _jwtService . GenerateToken ( user ) ;
// 生成 Refresh Token 并存储
// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Refresh Token <20> <> <EFBFBD> 洢
var refreshToken = await GenerateRefreshTokenAsync ( user . Id , ipAddress ) ;
// 计算 Access Token 过期时间(秒)
// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Access Token <20> <> <EFBFBD> <EFBFBD> ʱ<EFBFBD> 䣨<EFBFBD> 룩
var expiresIn = _jwtSettings . ExpirationMinutes * 60 ;
return new LoginResponse
@ -582,7 +586,7 @@ public class AuthService : IAuthService
}
/// <summary>
/// 刷新 Token
/// ˢ<EFBFBD> <EFBFBD> Token
/// Requirements: 2.1-2.6
/// </summary>
public async Task < RefreshTokenResult > RefreshTokenAsync ( string refreshToken , string? ipAddress )
@ -590,15 +594,15 @@ public class AuthService : IAuthService
if ( string . IsNullOrWhiteSpace ( refreshToken ) )
{
_logger . LogWarning ( "Refresh token is empty" ) ;
return RefreshTokenResult . Fail ( " 刷新令牌不能为空 ") ;
return RefreshTokenResult . Fail ( " ˢ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ʋ<EFBFBD> <EFBFBD> <EFBFBD> Ϊ<EFBFBD> <EFBFBD> ") ;
}
try
{
// 计算 Token 哈希值
// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Token <20> <> ϣֵ
var tokenHash = ComputeSha256Hash ( refreshToken ) ;
// 查找 Token 记录
// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Token <20> <> ¼
var storedToken = await _dbContext . UserRefreshTokens
. Include ( t = > t . User )
. FirstOrDefaultAsync ( t = > t . TokenHash = = tokenHash ) ;
@ -606,47 +610,47 @@ public class AuthService : IAuthService
if ( storedToken = = null )
{
_logger . LogWarning ( "Refresh token not found: {TokenHash}" , tokenHash . Substring ( 0 , 8 ) + "..." ) ;
return RefreshTokenResult . Fail ( " 无效的刷新令牌 ") ;
return RefreshTokenResult . Fail ( " <EFBFBD> <EFBFBD> Ч<EFBFBD> <EFBFBD> ˢ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ") ;
}
// 检查是否已过期
// <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 ( " 刷新令牌已过期 ") ;
return RefreshTokenResult . Fail ( " ˢ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ѹ<EFBFBD> <EFBFBD> <EFBFBD> ") ;
}
// 检查是否已撤销
// <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 ( " 刷新令牌已失效 ") ;
return RefreshTokenResult . Fail ( " ˢ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ʧЧ ") ;
}
// 检查用户是否存在且有效
// <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 ( " 用户不存在 ") ;
return RefreshTokenResult . Fail ( " <EFBFBD> û<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ") ;
}
if ( user . Status = = 0 )
{
_logger . LogWarning ( "User {UserId} is disabled" , user . Id ) ;
return RefreshTokenResult . Fail ( " 账号已被禁用 ") ;
return RefreshTokenResult . Fail ( " <EFBFBD> ˺<EFBFBD> <EFBFBD> ѱ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ") ;
}
// Token 轮换:生成新的 Refresh Token
// Token <EFBFBD> ֻ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> µ<EFBFBD> Refresh Token
var newRefreshToken = GenerateSecureRandomString ( RefreshTokenLength ) ;
var newTokenHash = ComputeSha256Hash ( newRefreshToken ) ;
// 撤销旧 Token 并记录关联关系
// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Token <20> <> <EFBFBD> <EFBFBD> ¼<EFBFBD> <C2BC> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ϵ
storedToken . RevokedAt = DateTime . Now ;
storedToken . RevokedByIp = ipAddress ;
storedToken . ReplacedByToken = newTokenHash ;
// 创建新的 Token 记录
// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> µ<EFBFBD> Token <20> <> ¼
var newUserRefreshToken = new UserRefreshToken
{
UserId = user . Id ,
@ -659,7 +663,7 @@ public class AuthService : IAuthService
await _dbContext . UserRefreshTokens . AddAsync ( newUserRefreshToken ) ;
await _dbContext . SaveChangesAsync ( ) ;
// 生成新的 Access Token
// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> µ<EFBFBD> Access Token
var accessToken = _jwtService . GenerateToken ( user ) ;
var expiresIn = _jwtSettings . ExpirationMinutes * 60 ;
@ -676,12 +680,12 @@ public class AuthService : IAuthService
catch ( Exception ex )
{
_logger . LogError ( ex , "Error refreshing token" ) ;
return RefreshTokenResult . Fail ( " 刷新令牌失败,请稍后重试 ") ;
return RefreshTokenResult . Fail ( " ˢ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ʧ<EFBFBD> ܣ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ժ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ") ;
}
}
/// <summary>
/// 撤销 Token
/// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Token
/// Requirements: 4.4
/// </summary>
public async Task RevokeTokenAsync ( string refreshToken , string? ipAddress )
@ -694,10 +698,10 @@ public class AuthService : IAuthService
try
{
// 计算 Token 哈希值
// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Token <20> <> ϣֵ
var tokenHash = ComputeSha256Hash ( refreshToken ) ;
// 查找 Token 记录
// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Token <20> <> ¼
var storedToken = await _dbContext . UserRefreshTokens
. FirstOrDefaultAsync ( t = > t . TokenHash = = tokenHash ) ;
@ -707,14 +711,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 ;
}
// 撤销 Token
// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Token
storedToken . RevokedAt = DateTime . Now ;
storedToken . RevokedByIp = ipAddress ;
@ -730,14 +734,14 @@ public class AuthService : IAuthService
}
/// <summary>
/// 撤销用户的所有 Token
/// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> û<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Token
/// Requirements: 4.4
/// </summary>
public async Task RevokeAllUserTokensAsync ( long userId , string? ipAddress )
{
try
{
// 查找用户所有有效的 Token
// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> û<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ч<EFBFBD> <EFBFBD> Token
var activeTokens = await _dbContext . UserRefreshTokens
. Where ( t = > t . UserId = = userId & & t . RevokedAt = = null )
. ToListAsync ( ) ;
@ -771,7 +775,7 @@ public class AuthService : IAuthService
#region Private Helper Methods
/// <summary>
/// 合并账户 - 将当前用户的openid迁移到手机号用户
/// <EFBFBD> ϲ <EFBFBD> <EFBFBD> ˻<EFBFBD> - <20> <> <EFBFBD> <EFBFBD> ǰ<EFBFBD> û<EFBFBD> <C3BB> <EFBFBD> openidǨ<64> Ƶ<EFBFBD> <C6B5> ֻ<EFBFBD> <D6BB> <EFBFBD> <EFBFBD> û<EFBFBD>
/// </summary>
private async Task < BindMobileResponse > MergeAccountsAsync ( User currentUser , User mobileUser )
{
@ -781,7 +785,7 @@ public class AuthService : IAuthService
_logger . LogInformation ( "Merging accounts: CurrentUserId={CurrentUserId}, MobileUserId={MobileUserId}" ,
currentUser . Id , mobileUser . Id ) ;
// 5.2 将当前用户的openid迁移到手机号用户
// 5.2 <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ǰ<EFBFBD> û<EFBFBD> <EFBFBD> <EFBFBD> openidǨ<EFBFBD> Ƶ<EFBFBD> <EFBFBD> ֻ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> û<EFBFBD>
if ( ! string . IsNullOrWhiteSpace ( currentUser . OpenId ) )
{
mobileUser . OpenId = currentUser . OpenId ;
@ -793,13 +797,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 生成新的 token
// 5.3 <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> µ<EFBFBD> token
var newToken = _jwtService . GenerateToken ( mobileUser ) ;
_logger . LogInformation ( "Accounts merged successfully: NewUserId={NewUserId}" , mobileUser . Id ) ;
@ -816,18 +820,70 @@ public class AuthService : IAuthService
}
/// <summary>
/// 生成默认头像URL
/// 获取用户默认昵称(从配置读取前缀)
/// </summary>
private async Task < string > GetDefaultNicknameAsync ( )
{
try
{
var configJson = await _configService . GetConfigValueAsync ( "user_config" ) ;
if ( ! string . IsNullOrEmpty ( configJson ) )
{
var config = JsonSerializer . Deserialize < JsonElement > ( 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)}" ;
}
/// <summary>
/// 获取用户默认头像(从配置读取,为空则自动生成)
/// </summary>
private async Task < string > GetDefaultAvatarAsync ( string seed )
{
try
{
var configJson = await _configService . GetConfigValueAsync ( "user_config" ) ;
if ( ! string . IsNullOrEmpty ( configJson ) )
{
var config = JsonSerializer . Deserialize < JsonElement > ( 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 ) ;
}
/// <summary>
/// <20> <> <EFBFBD> <EFBFBD> Ĭ<EFBFBD> <C4AC> ͷ<EFBFBD> <CDB7> URL
/// </summary>
private static string GenerateDefaultAvatar ( string seed )
{
// 使用种子生成一个简单的默认头像URL
// 实际项目中可以使用Identicon库或其他头像生成服务
// ʹ <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>
var hash = ComputeMd5 ( seed ) ;
return $"https://api.dicebear.com/7.x/identicon/svg?seed={hash}" ;
}
/// <summary>
/// 计算MD5哈希
/// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> MD5<EFBFBD> <EFBFBD> ϣ
/// </summary>
private static string ComputeMd5 ( string input )
{
@ -837,7 +893,7 @@ public class AuthService : IAuthService
}
/// <summary>
/// 生成随机字符串
/// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ַ<EFBFBD> <EFBFBD> <EFBFBD>
/// </summary>
private static string GenerateRandomString ( int length )
{
@ -851,7 +907,7 @@ public class AuthService : IAuthService
}
/// <summary>
/// 脱敏手机号
/// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ֻ<EFBFBD> <EFBFBD> <EFBFBD>
/// </summary>
private static string MaskMobile ( string mobile )
{
@ -862,7 +918,7 @@ public class AuthService : IAuthService
}
/// <summary>
/// 获取年份中的周数
/// <EFBFBD> <EFBFBD> ȡ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> е <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD>
/// </summary>
private static int GetWeekOfYear ( DateTime date )
{
@ -871,7 +927,7 @@ public class AuthService : IAuthService
}
/// <summary>
/// 计算 SHA256 哈希值
/// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> SHA256 <20> <> ϣֵ
/// Requirements: 4.1
/// </summary>
private static string ComputeSha256Hash ( string input )
@ -882,7 +938,7 @@ public class AuthService : IAuthService
}
/// <summary>
/// 生成安全的随机字符串(用于 Refresh Token)
/// <EFBFBD> <EFBFBD> <EFBFBD> ɰ<EFBFBD> ȫ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ַ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Refresh Token<65> <6E>
/// </summary>
private static string GenerateSecureRandomString ( int length )
{