using System; using System.Text; using System.Threading.Tasks; using IchniOnline.Online.Network; using IchniOnline.Online.Network.Models; using TapSDK.Login; using UnityEngine; namespace IchniOnline.Online.Network.Models { [Serializable] public class SessionKeyResponseDto { public string SessionKey; public long ExpiresAt; } } namespace IchniOnline.Online.Logic { /// /// Authentication orchestration service that coordinates TapTap login, /// password login, registration, and logout flows. /// Pure orchestration — no UI state management. Results are delivered via events. /// public static class IchniOnlineAuthService { /// /// 登录成功时触发,参数为服务端返回的登录响应(Register 成功时可能为 null) /// public static event Action OnLoginSuccess; /// /// 登录失败时触发,参数为错误信息 /// public static event Action OnLoginFailed; /// /// 登录被用户取消时触发 /// public static event Action OnLoginCanceled; /// /// 是否正在进行登录流程,用于防止并发登录请求 /// public static bool IsLoggingIn { get; private set; } #region TapTap Login /// /// 使用 TapTap 登录的完整流程: /// TapTap SDK → 第三方登录 API → JWT → 缓存 → 事件 /// public static async void LoginWithTapTap() { if (IsLoggingIn) { Debug.LogWarning("[IchniOnlineAuthService] 已有登录流程正在进行中"); return; } IsLoggingIn = true; Action onSuccess = null; Action onCanceled = null; Action onFailed = null; onSuccess = async account => { UnsubscribeTapTapEvents(onSuccess, onCanceled, onFailed); if (account?.accessToken == null) { IsLoggingIn = false; OnLoginFailed?.Invoke("TapTap 登录成功但 accessToken 为空"); return; } var dto = new ThirdPartyLoginRequestDto { Token = account.accessToken.kid, TokenType = account.accessToken.tokenType, MacKey = account.accessToken.macKey, MacAlgorithm = account.accessToken.macAlgorithm }; try { var result = await IchniOnlineApiClient.Instance.PostAsync("/api/auth/third-party/login", dto); IsLoggingIn = false; if (result.IsSuccess) { LoginCacheManager.SaveAuthSession(result.Data.Token, result.Data); IchniOnlineApiClient.Instance.JwtToken = result.Data.Token; OnLoginSuccess?.Invoke(result.Data); } else { string errorMessage = $"第三方登录 API 失败: {result.Message}"; if (!string.IsNullOrEmpty(result.ErrorDetail)) errorMessage += $" ({result.ErrorDetail})"; OnLoginFailed?.Invoke(errorMessage); } } catch (Exception ex) { IsLoggingIn = false; Debug.LogError($"[IchniOnlineAuthService] TapTap 登录 API 异常: {ex}"); OnLoginFailed?.Invoke($"TapTap 登录 API 异常: {ex.Message}"); } }; onCanceled = () => { UnsubscribeTapTapEvents(onSuccess, onCanceled, onFailed); IsLoggingIn = false; OnLoginCanceled?.Invoke(); }; onFailed = errorMessage => { UnsubscribeTapTapEvents(onSuccess, onCanceled, onFailed); IsLoggingIn = false; OnLoginFailed?.Invoke($"TapTap 登录失败: {errorMessage}"); }; ThirdPartyServiceManager.Instance.OnLoginSuccess += onSuccess; ThirdPartyServiceManager.Instance.OnLoginCanceled += onCanceled; ThirdPartyServiceManager.Instance.OnLoginFailed += onFailed; ThirdPartyServiceManager.Instance.StartTapTapLogin(); } private static void UnsubscribeTapTapEvents( Action onSuccess, Action onCanceled, Action onFailed) { if (ThirdPartyServiceManager.Instance != null) { ThirdPartyServiceManager.Instance.OnLoginSuccess -= onSuccess; ThirdPartyServiceManager.Instance.OnLoginCanceled -= onCanceled; ThirdPartyServiceManager.Instance.OnLoginFailed -= onFailed; } } #endregion #region Password Login /// /// 使用用户名和密码登录的完整流程: /// 获取 session-key → XOR 加密密码 → 登录 API → JWT → 缓存 → 事件 /// public static async void LoginWithPassword(string username, string password) { if (IsLoggingIn) { Debug.LogWarning("[IchniOnlineAuthService] 已有登录流程正在进行中"); return; } if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) { OnLoginFailed?.Invoke("用户名或密码不能为空"); return; } IsLoggingIn = true; try { // 1. 获取 session-key var sessionResult = await IchniOnlineApiClient.Instance.GetAsync("/api/auth/session-key"); if (!sessionResult.IsSuccess) { IsLoggingIn = false; string errorMessage = $"获取 session-key 失败: {sessionResult.Message}"; if (!string.IsNullOrEmpty(sessionResult.ErrorDetail)) errorMessage += $" ({sessionResult.ErrorDetail})"; OnLoginFailed?.Invoke(errorMessage); return; } string sessionKey = sessionResult.Data.SessionKey; // 2. XOR 加密密码 string encryptedPassword = EncryptPassword(password, sessionKey); // 3. 调用登录 API var loginDto = new LoginRequestDto { Username = username, EncryptedPassword = encryptedPassword, SessionKey = sessionKey }; var loginResult = await IchniOnlineApiClient.Instance.PostAsync("/api/auth/login", loginDto); IsLoggingIn = false; if (loginResult.IsSuccess) { LoginCacheManager.SaveAuthSession(loginResult.Data.Token, loginResult.Data); IchniOnlineApiClient.Instance.JwtToken = loginResult.Data.Token; OnLoginSuccess?.Invoke(loginResult.Data); } else { string errorMessage = $"登录失败: {loginResult.Message}"; if (!string.IsNullOrEmpty(loginResult.ErrorDetail)) errorMessage += $" ({loginResult.ErrorDetail})"; OnLoginFailed?.Invoke(errorMessage); } } catch (Exception ex) { IsLoggingIn = false; Debug.LogError($"[IchniOnlineAuthService] 密码登录异常: {ex}"); OnLoginFailed?.Invoke($"登录异常: {ex.Message}"); } } #endregion #region Register /// /// 用户注册流程:POST /api/auth/register /// 注册成功后触发 OnLoginSuccess(null)(注册不返回 JWT) /// public static async void Register(string username, string password, string displayName) { if (IsLoggingIn) { Debug.LogWarning("[IchniOnlineAuthService] 已有登录流程正在进行中"); return; } if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) { OnLoginFailed?.Invoke("用户名或密码不能为空"); return; } IsLoggingIn = true; try { var registerDto = new RegisterRequestDto { Username = username, Password = password, DisplayName = displayName }; var result = await IchniOnlineApiClient.Instance.PostAsync("/api/auth/register", registerDto); IsLoggingIn = false; if (result.IsSuccess) { OnLoginSuccess?.Invoke(null); } else { string errorMessage = $"注册失败: {result.Message}"; if (!string.IsNullOrEmpty(result.ErrorDetail)) errorMessage += $" ({result.ErrorDetail})"; OnLoginFailed?.Invoke(errorMessage); } } catch (Exception ex) { IsLoggingIn = false; Debug.LogError($"[IchniOnlineAuthService] 注册异常: {ex}"); OnLoginFailed?.Invoke($"注册异常: {ex.Message}"); } } #endregion #region Logout /// /// 登出流程:清除本地会话、登出 TapTap、清除 API JWT Token /// public static void Logout() { LoginCacheManager.ClearSession(); ThirdPartyServiceManager.Instance?.Logout(); IchniOnlineApiClient.Instance.JwtToken = null; Debug.Log("[IchniOnlineAuthService] 已登出"); } #endregion #region Encryption /// /// 使用 XOR 算法加密密码,与服务器 UserService.DecryptPassword 对应。 /// sessionKey 为服务器返回的 Base64 字符串。 /// public static string EncryptPassword(string password, string sessionKey) { if (string.IsNullOrEmpty(password)) throw new ArgumentException("密码不能为空", nameof(password)); if (string.IsNullOrEmpty(sessionKey)) throw new ArgumentException("sessionKey 不能为空", nameof(sessionKey)); byte[] passwordBytes = Encoding.UTF8.GetBytes(password); byte[] sessionBytes = Convert.FromBase64String(sessionKey); byte[] encrypted = new byte[passwordBytes.Length]; for (int i = 0; i < passwordBytes.Length; i++) { encrypted[i] = (byte)(passwordBytes[i] ^ sessionBytes[i % sessionBytes.Length]); } return Convert.ToBase64String(encrypted); } #endregion } }