update
This commit is contained in:
@@ -16,7 +16,8 @@
|
||||
"GUID:d9925423e828d479c9063ea882f31e06",
|
||||
"GUID:cfcd2ce455f8d1944942cdd919ecaa60",
|
||||
"GUID:9069ac25d95ca17448b247f3bb1c769f",
|
||||
"GUID:9069ac25d95ca17448a247f3bb1c769f"
|
||||
"GUID:9069ac25d95ca17448a247f3bb1c769f",
|
||||
"GUID:f51ebe6a0ceec4240a699833d6309b23"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
|
||||
@@ -1,21 +1,11 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Cysharp.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
|
||||
{
|
||||
/// <summary>
|
||||
@@ -51,7 +41,7 @@ namespace IchniOnline.Online.Logic
|
||||
/// 使用 TapTap 登录的完整流程:
|
||||
/// TapTap SDK → 第三方登录 API → JWT → 缓存 → 事件
|
||||
/// </summary>
|
||||
public static async void LoginWithTapTap()
|
||||
public static void LoginWithTapTap()
|
||||
{
|
||||
if (IsLoggingIn)
|
||||
{
|
||||
@@ -65,51 +55,11 @@ namespace IchniOnline.Online.Logic
|
||||
Action onCanceled = null;
|
||||
Action<string> onFailed = null;
|
||||
|
||||
onSuccess = async account =>
|
||||
onSuccess = 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<LoginResponseDto>("/api/auth/third-party/login", dto);
|
||||
Debug.Log(JsonUtility.ToJson(result.Data));
|
||||
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}");
|
||||
}
|
||||
CompleteTapTapLoginAsync(account).Forget();
|
||||
};
|
||||
|
||||
onCanceled = () =>
|
||||
@@ -133,6 +83,55 @@ namespace IchniOnline.Online.Logic
|
||||
ThirdPartyServiceManager.Instance.StartTapTapLogin();
|
||||
}
|
||||
|
||||
private static async UniTaskVoid CompleteTapTapLoginAsync(TapTapAccount account)
|
||||
{
|
||||
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<LoginResponseDto>("/api/auth/third-party/login", dto);
|
||||
Debug.Log(JsonUtility.ToJson(result.Data));
|
||||
IsLoggingIn = false;
|
||||
|
||||
if (result.IsSuccess && result.Data != null)
|
||||
{
|
||||
LoginCacheManager.SaveAuthSession(result.Data.token, result.Data);
|
||||
IchniOnlineApiClient.Instance.JwtToken = result.Data.token;
|
||||
OnLoginSuccess?.Invoke(result.Data);
|
||||
}
|
||||
else if (result.IsSuccess && result.Data == null)
|
||||
{
|
||||
OnLoginFailed?.Invoke("TapTap login successful but account not bound");
|
||||
}
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void UnsubscribeTapTapEvents(
|
||||
Action<TapTapAccount> onSuccess,
|
||||
Action onCanceled,
|
||||
@@ -154,7 +153,7 @@ namespace IchniOnline.Online.Logic
|
||||
/// 使用用户名和密码登录的完整流程:
|
||||
/// 获取 session-key → XOR 加密密码 → 登录 API → JWT → 缓存 → 事件
|
||||
/// </summary>
|
||||
public static async void LoginWithPassword(string username, string password)
|
||||
public static void LoginWithPassword(string username, string password)
|
||||
{
|
||||
if (IsLoggingIn)
|
||||
{
|
||||
@@ -170,10 +169,15 @@ namespace IchniOnline.Online.Logic
|
||||
|
||||
IsLoggingIn = true;
|
||||
|
||||
LoginWithPasswordAsync(username, password).Forget();
|
||||
}
|
||||
|
||||
private static async UniTaskVoid LoginWithPasswordAsync(string username, string password)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 1. 获取 session-key
|
||||
var sessionResult = await IchniOnlineApiClient.Instance.GetAsync<SessionKeyResponseDto>("/api/auth/session-key");
|
||||
var sessionResult = await IchniOnlineApiClient.Instance.GetAsync<string>("/api/auth/session-key");
|
||||
if (!sessionResult.IsSuccess)
|
||||
{
|
||||
IsLoggingIn = false;
|
||||
@@ -184,7 +188,7 @@ namespace IchniOnline.Online.Logic
|
||||
return;
|
||||
}
|
||||
|
||||
string sessionKey = sessionResult.Data.SessionKey;
|
||||
string sessionKey = sessionResult.Data;
|
||||
|
||||
// 2. XOR 加密密码
|
||||
string encryptedPassword = EncryptPassword(password, sessionKey);
|
||||
@@ -192,9 +196,9 @@ namespace IchniOnline.Online.Logic
|
||||
// 3. 调用登录 API
|
||||
var loginDto = new LoginRequestDto
|
||||
{
|
||||
Username = username,
|
||||
EncryptedPassword = encryptedPassword,
|
||||
SessionKey = sessionKey
|
||||
username = username,
|
||||
encryptedPassword = encryptedPassword,
|
||||
sessionKey = sessionKey
|
||||
};
|
||||
|
||||
var loginResult = await IchniOnlineApiClient.Instance.PostAsync<LoginResponseDto>("/api/auth/login", loginDto);
|
||||
@@ -203,8 +207,8 @@ namespace IchniOnline.Online.Logic
|
||||
|
||||
if (loginResult.IsSuccess)
|
||||
{
|
||||
LoginCacheManager.SaveAuthSession(loginResult.Data.Token, loginResult.Data);
|
||||
IchniOnlineApiClient.Instance.JwtToken = loginResult.Data.Token;
|
||||
LoginCacheManager.SaveAuthSession(loginResult.Data.token, loginResult.Data);
|
||||
IchniOnlineApiClient.Instance.JwtToken = loginResult.Data.token;
|
||||
OnLoginSuccess?.Invoke(loginResult.Data);
|
||||
}
|
||||
else
|
||||
@@ -231,7 +235,7 @@ namespace IchniOnline.Online.Logic
|
||||
/// 用户注册流程:POST /api/auth/register
|
||||
/// 注册成功后触发 OnLoginSuccess(null)(注册不返回 JWT)
|
||||
/// </summary>
|
||||
public static async void Register(string username, string password, string displayName)
|
||||
public static void Register(string username, string password, string displayName)
|
||||
{
|
||||
if (IsLoggingIn)
|
||||
{
|
||||
@@ -247,16 +251,21 @@ namespace IchniOnline.Online.Logic
|
||||
|
||||
IsLoggingIn = true;
|
||||
|
||||
RegisterAsync(username, password, displayName).Forget();
|
||||
}
|
||||
|
||||
private static async UniTaskVoid RegisterAsync(string username, string password, string displayName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var registerDto = new RegisterRequestDto
|
||||
{
|
||||
Username = username,
|
||||
Password = password,
|
||||
DisplayName = displayName
|
||||
username = username,
|
||||
password = password,
|
||||
displayName = displayName
|
||||
};
|
||||
|
||||
var result = await IchniOnlineApiClient.Instance.PostAsync<object>("/api/auth/register", registerDto);
|
||||
var result = await IchniOnlineApiClient.Instance.PostAsync<UserResponseDto>("/api/auth/register", registerDto);
|
||||
|
||||
IsLoggingIn = false;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Sirenix.OdinInspector;
|
||||
using TapSDK.Core;
|
||||
using TapSDK.Login;
|
||||
@@ -11,7 +12,6 @@ namespace IchniOnline.Online.Logic
|
||||
public class ThirdPartyServiceManager:SerializedMonoBehaviour
|
||||
{
|
||||
public static ThirdPartyServiceManager Instance { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// TapTap 登录成功时触发,参数为登录获得的 TapTapAccount
|
||||
/// </summary>
|
||||
@@ -33,7 +33,17 @@ namespace IchniOnline.Online.Logic
|
||||
/// </summary>
|
||||
public event Action<TapTapAccount, AccessToken> OnLoginWithToken;
|
||||
|
||||
private bool _initialized;
|
||||
// 使用 static 字段防止 Domain Reload 后重复初始化 TapSDK 原生模块导致崩溃
|
||||
private static bool _sdkInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// Domain Reload 时重置 static 状态,确保 Play Mode 正确重新初始化
|
||||
/// </summary>
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
static void ResetStatic()
|
||||
{
|
||||
_sdkInitialized = false;
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
@@ -54,7 +64,7 @@ namespace IchniOnline.Online.Logic
|
||||
|
||||
private void InitializeTapTapSDK()
|
||||
{
|
||||
if (_initialized) return;
|
||||
if (_sdkInitialized) return;
|
||||
|
||||
// 核心配置 详细参数见 [TapTapSDK]
|
||||
TapTapSdkOptions coreOptions = new TapTapSdkOptions()
|
||||
@@ -65,21 +75,31 @@ namespace IchniOnline.Online.Logic
|
||||
preferredLanguage = TapTapLanguageType.en,
|
||||
enableLog = true
|
||||
};
|
||||
// TapSDK 初始化,但是这玩意目前极易崩unity
|
||||
#if false
|
||||
TapTapSDK.Init(coreOptions);
|
||||
_initialized = true;
|
||||
#endif
|
||||
|
||||
try
|
||||
{
|
||||
TapTapSDK.Init(coreOptions);
|
||||
_sdkInitialized = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"[ThirdPartyServiceManager] TapSDK 初始化失败: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发起 TapTap 登录,由 UI 按钮调用。
|
||||
/// 登录结果通过事件 OnLoginSuccess / OnLoginCanceled / OnLoginFailed 通知。
|
||||
/// </summary>
|
||||
public async void StartTapTapLogin()
|
||||
public void StartTapTapLogin()
|
||||
{
|
||||
StartTapTapLoginAsync().Forget();
|
||||
}
|
||||
|
||||
private async UniTaskVoid StartTapTapLoginAsync()
|
||||
{
|
||||
// 确保 SDK 已初始化
|
||||
if (!_initialized)
|
||||
if (!_sdkInitialized)
|
||||
{
|
||||
InitializeTapTapSDK();
|
||||
}
|
||||
@@ -120,4 +140,4 @@ namespace IchniOnline.Online.Logic
|
||||
Debug.Log("TapTap 已登出");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,11 +41,11 @@ namespace IchniOnline.Online.Models
|
||||
|
||||
public void UpdateFromServerResponse(LoginResponseDto response)
|
||||
{
|
||||
this.jwtToken = response.Token;
|
||||
this.userId = response.User.UserId;
|
||||
this.displayName = response.User.DisplayName;
|
||||
this.avatarUrl = response.User.AvatarUrl;
|
||||
this.permission = response.User.Permission;
|
||||
this.jwtToken = response.token;
|
||||
this.userId = response.user.userId;
|
||||
this.displayName = response.user.displayName;
|
||||
this.avatarUrl = response.user.avatarUrl;
|
||||
this.permission = response.user.permission;
|
||||
this.hasServerSession = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Best.HTTP;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using IchniOnline.Online.Network.Models;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -17,12 +17,12 @@ namespace IchniOnline.Online.Network
|
||||
private static IchniOnlineApiClient _instance;
|
||||
public static IchniOnlineApiClient Instance => _instance ??= new IchniOnlineApiClient();
|
||||
|
||||
public string BaseUrl { get; set; } = "http://localhost:53734";
|
||||
public string BaseUrl { get; set; } = "http://localhost:5308";
|
||||
public string JwtToken { get; set; }
|
||||
|
||||
private IchniOnlineApiClient() { }
|
||||
|
||||
public async Task<ApiResult<T>> GetAsync<T>(string endpoint)
|
||||
public async UniTask<ApiResult<T>> GetAsync<T>(string endpoint)
|
||||
{
|
||||
string url = BuildUrl(endpoint);
|
||||
var request = new HTTPRequest(new Uri(url), HTTPMethods.Get);
|
||||
@@ -30,7 +30,7 @@ namespace IchniOnline.Online.Network
|
||||
|
||||
try
|
||||
{
|
||||
var resp = await request.GetHTTPResponseAsync();
|
||||
var resp = await SendAsync(request);
|
||||
return ProcessResponse<T>(resp);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -39,7 +39,7 @@ namespace IchniOnline.Online.Network
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ApiResult<T>> PostAsync<T>(string endpoint, object body)
|
||||
public async UniTask<ApiResult<T>> PostAsync<T>(string endpoint, object body)
|
||||
{
|
||||
string url = BuildUrl(endpoint);
|
||||
var request = new HTTPRequest(new Uri(url), HTTPMethods.Post);
|
||||
@@ -54,7 +54,7 @@ namespace IchniOnline.Online.Network
|
||||
|
||||
try
|
||||
{
|
||||
var resp = await request.GetHTTPResponseAsync();
|
||||
var resp = await SendAsync(request);
|
||||
return ProcessResponse<T>(resp);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -81,6 +81,30 @@ namespace IchniOnline.Online.Network
|
||||
}
|
||||
}
|
||||
|
||||
private UniTask<HTTPResponse> SendAsync(HTTPRequest request)
|
||||
{
|
||||
var completionSource = new UniTaskCompletionSource<HTTPResponse>();
|
||||
|
||||
request.Callback = (req, resp) =>
|
||||
{
|
||||
switch (req.State)
|
||||
{
|
||||
case HTTPRequestStates.Finished:
|
||||
completionSource.TrySetResult(resp);
|
||||
break;
|
||||
case HTTPRequestStates.Aborted:
|
||||
completionSource.TrySetCanceled();
|
||||
break;
|
||||
default:
|
||||
completionSource.TrySetException(req.Exception ?? new Exception($"HTTP request failed: {req.State}"));
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
request.Send();
|
||||
return completionSource.Task;
|
||||
}
|
||||
|
||||
private ApiResult<T> ProcessResponse<T>(HTTPResponse resp)
|
||||
{
|
||||
string json = resp.DataAsText;
|
||||
@@ -98,12 +122,12 @@ namespace IchniOnline.Online.Network
|
||||
return ApiResult<T>.Fail(ResponseCode.InternalServerError, "Failed to parse response JSON");
|
||||
}
|
||||
|
||||
if (response.Code == ResponseCode.Ok)
|
||||
if (response.code == ResponseCode.Ok)
|
||||
{
|
||||
return ApiResult<T>.Ok(response.Data);
|
||||
return ApiResult<T>.Ok(response.data);
|
||||
}
|
||||
|
||||
return ApiResult<T>.Fail(response.Code, response.Message);
|
||||
return ApiResult<T>.Fail(response.code, response.message);
|
||||
}
|
||||
|
||||
// Non-2xx: try to parse server error body
|
||||
@@ -112,7 +136,7 @@ namespace IchniOnline.Online.Network
|
||||
var errorResponse = JsonUtility.FromJson(json, typeof(GlobalResponseBase)) as GlobalResponseBase;
|
||||
if (errorResponse != null)
|
||||
{
|
||||
return ApiResult<T>.Fail(errorResponse.Code, errorResponse.Message);
|
||||
return ApiResult<T>.Fail(errorResponse.code, errorResponse.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ namespace IchniOnline.Online.Network.Models
|
||||
[System.Serializable]
|
||||
public abstract class GlobalResponseBase
|
||||
{
|
||||
public ResponseCode Code;
|
||||
public string Message;
|
||||
public ResponseCode code;
|
||||
public string message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -32,7 +32,7 @@ namespace IchniOnline.Online.Network.Models
|
||||
[System.Serializable]
|
||||
public class GlobalResponse<T> : GlobalResponseBase
|
||||
{
|
||||
public T Data;
|
||||
public T data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -5,42 +5,42 @@ namespace IchniOnline.Online.Network.Models
|
||||
[Serializable]
|
||||
public class ThirdPartyLoginRequestDto
|
||||
{
|
||||
public string Token;
|
||||
public string TokenType;
|
||||
public string MacKey;
|
||||
public string MacAlgorithm;
|
||||
public string token;
|
||||
public string tokenType;
|
||||
public string macKey;
|
||||
public string macAlgorithm;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class LoginRequestDto
|
||||
{
|
||||
public string Username;
|
||||
public string EncryptedPassword;
|
||||
public string SessionKey;
|
||||
public string username;
|
||||
public string encryptedPassword;
|
||||
public string sessionKey;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class RegisterRequestDto
|
||||
{
|
||||
public string Username;
|
||||
public string Password;
|
||||
public string DisplayName;
|
||||
public string username;
|
||||
public string password;
|
||||
public string displayName;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class LoginResponseDto
|
||||
{
|
||||
public string Token;
|
||||
public UserResponseDto User;
|
||||
public string token;
|
||||
public UserResponseDto user;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class UserResponseDto
|
||||
{
|
||||
public string UserId;
|
||||
public string Username;
|
||||
public string DisplayName;
|
||||
public string AvatarUrl;
|
||||
public int Permission;
|
||||
public string userId;
|
||||
public string username;
|
||||
public string displayName;
|
||||
public string avatarUrl;
|
||||
public int permission;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user