# Plan: IchniOnline API Integration ## TL;DR > **Quick Summary**: Create an HTTP API client layer using BestHTTP and implement full auth flows (TapTap third-party login, password login, registration) to connect the Unity game client to the IchniOnline backend server. > > **Deliverables**: > - BestHTTP-based API client with JSON serialization, JWT injection, error handling > - Request/Response DTOs matching server contracts > - Auth orchestration service (TapTap login, password login, register, logout) > - Extended LoginCacheData with JWT + server user data > - Updated LoginPage/StartUIPage to use new auth service > > **Estimated Effort**: Medium > **Parallel Execution**: YES — 4 waves > **Critical Path**: asmdef → ApiClient → AuthService → UI Integration --- ## Context ### Original Request 对接 IchniOnline 后端的 API 接口,使用 BestHTTP 进行 HTTP 通信,编写详细计划。所有代码写在 `Scripts/Online` 目录内(IchniOnline.asmdef)。 ### Interview Summary **Key Discussions**: - 服务器开发地址: `localhost:5433`,可配置 Base URL - ThirdParty.Unbound: 暂时跳过,当做登录失败处理 - JWT 存储: 扩展 LoginCacheData,加入 JWT + 用户数据,取代纯 TapTap 缓存 - 计划范围: TapTap 登录 + 密码登录/注册 + 基础 API 框架 **Research Findings**: - **BestHTTP** 已安装为 embedded package (`com.tivadar.best.http`),GUID: `9069ac25d95ca17448a247f3bb1c769f`,支持 async/await via `Task` - **TapTap SDK** 登录返回 `TapTapAccount.accessToken`(kid/tokenType/macKey/macAlgorithm),可直接映射到 `ThirdPartyLoginRequest` - **服务端密码加密**: XOR with session key(见 `UserService.DecryptPassword`) - **服务端响应格式**: `GlobalResponse { Code, Message, Data }`,Code 10000=Ok - **JWT 验证**: 服务端使用 JWT Bearer,后续 API 请求需在 Header 注入 `Authorization: Bearer {token}` ### Metis Review *(Skipped per user request — direct exploration used instead)* --- ## Work Objectives ### Core Objective 为 Unity 客户端创建 IchniOnline 后端 API 对接层,实现完整的认证流程(TapTap 第三方登录、密码登录、注册),让客户端和服务端互通。 ### Concrete Deliverables - `Scripts/Online/IchniOnline.asmdef` — 添加 BestHTTP 引用 - `Scripts/Online/Network/ApiClient.cs` — BestHTTP 封装单例 - `Scripts/Online/Network/Models/ApiResponse.cs` — 响应模型 - `Scripts/Online/Network/Models/AuthDtos.cs` — 请求/响应 DTO - `Scripts/Online/Logic/AuthService.cs` — 认证编排服务 - `Scripts/Online/Logic/ThirdPartyServiceManager.cs` — 修改:集成 AuthService - `Scripts/Online/Logic/LoginCacheManager.cs` — 修改:扩展 JWT 支持 - `Scripts/Online/Models/LoginCacheData.cs` — 修改:添加 JWT 字段 ### Definition of Done - [ ] 编译零错误,无警告 - [ ] TapTap 登录后 JWT Token 成功写入本地缓存 - [ ] 密码登录流程完整(session key → XOR 加密 → login → JWT) - [ ] 注册流程完整 - [ ] LoginPage 使用新 AuthService 驱动流程 - [ ] StartUIPage 校验会话有效性 ### Must Have - ApiClient 支持 Base URL 配置(运行时可修改) - ApiClient 自动注入 JWT Bearer 到 Authorization Header - 所有 DTO 映射与服务器 `GlobalResponse` 完全匹配 - 密码 XOR 加密算法与服务器端一致 - AuthService 对外暴露事件(OnLoginSuccess/OnLoginFailed/etc.) - LoginCacheData 向后兼容(旧缓存不报错) ### Must NOT Have (Guardrails) - 不要引入新的第三方 HTTP 库(只用 BestHTTP) - 不要修改 `LoginCacheEditor.cs`(但可扩展) - 不要修改 `UI/Base/UIPageBase.cs` - 不要引入 Newtonsoft.Json(用 BestHTTP 内置的 LitJson 或 UnityEngine.JsonUtility) - 不要阻塞主线程(所有 HTTP 请求必须异步) --- ## Verification Strategy ### Test Decision - **Infrastructure exists**: YES (Unity Test Framework) - **Automated tests**: None for network layer (needs running server) - **Agent-Executed QA**: ALWAYS — each task verified via Playwright/browser or manual Unity Editor play mode evidence ### QA Policy Every task MUST include agent-executed QA scenarios. - C# compilation through Unity Editor domain reload (verify zero compilation errors) - Evidence: Unity Editor console logs, screenshots of play mode, runtime log output --- ## Execution Strategy ### Parallel Execution Waves ``` Wave 1 (Foundation — all parallel): ├── Task 1: IchniOnline.asmdef — 添加 BestHTTP 引用 [quick] ├── Task 2: ApiResponse.cs — GlobalResponse 响应模型 [quick] ├── Task 3: AuthDtos.cs — 请求/响应 DTO [quick] └── Task 4: LoginCacheData.cs — 扩展 JWT 字段 [quick] Wave 2 (Core Client — depends on Wave 1): ├── Task 5: ApiClient.cs — BestHTTP 封装单例 [unspecified-high] └── Task 6: LoginCacheManager.cs — 扩展 JWT 存取 [quick] Note: Task 5+6 can run in parallel Wave 3 (Auth Service — depends on Task 5+6): ├── Task 7: AuthService.cs — 认证编排服务 [unspecified-high] Note: Depends on ApiClient + LoginCacheManager Wave 4 (UI Integration — depends on Task 7): ├── Task 8: ThirdPartyServiceManager.cs — 集成 AuthService [quick] ├── Task 9: LoginPage.cs — 使用新 AuthService [visual-engineering] └── Task 10: StartUIPage.cs — 校验会话有效性 [visual-engineering] Wave FINAL: ├── F1: Plan Compliance Audit (oracle) ├── F2: Code Quality Review (unspecified-high) ├── F3: Real Manual QA (unspecified-high) └── F4: Scope Fidelity Check (deep) ``` ### Dependency Matrix - **Task 1-4**: - → 5, 6 → Wave 2 - **Task 5, 6**: 1-4 → 7 → Wave 3 - **Task 7**: 5, 6 → 8-10 → Wave 4 - **Task 8-10**: 7 → F1-F4 → Final --- ## TODOs - [x] 1. IchniOnline.asmdef — 添加 BestHTTP 引用 **What to do**: - 在 `IchniOnline.asmdef` 的 `references` 数组中追加 BestHTTP 的 GUID - BestHTTP Runtime asmdef GUID: `9069ac25d95ca17448a247f3bb1c769f` - 验证 Unity 编译无误,IchniOnline 程序集可以 `using Best.HTTP;` **Must NOT do**: - 不要修改其他 asmdef - 不要修改 IchniOnline.Editor.asmdef **Recommended Agent Profile**: - **Category**: `quick` - Reason: 单行 asmdef 修改,极简单的任务 - **Skills**: None needed **Parallelization**: - **Can Run In Parallel**: YES - **Parallel Group**: Wave 1 (with Tasks 2, 3, 4) - **Blocks**: Tasks 5, 6 - **Blocked By**: None **References**: - `Assets/Scripts/Online/IchniOnline.asmdef` — 目标文件 - `Packages/com.tivadar.best.http/Runtime/com.Tivadar.Best.HTTP.asmdef.meta:L1-7` — GUID: `9069ac25d95ca17448a247f3bb1c769f` **Acceptance Criteria**: - [ ] `IchniOnline.asmdef` 的 `references` 数组包含 `"GUID:9069ac25d95ca17448a247f3bb1c769f"` - [ ] Unity 编译完成后,IchniOnline 程序集内可以 `using Best.HTTP;` 无报错 **QA Scenarios**: ``` Scenario: asmdef 引用验证 Tool: Bash (配合 Unity Editor) Preconditions: Unity Editor 已打开,IchniOnline.asmdef 已修改 Steps: 1. 在 Unity 中等待编译完成(AssetDatabase.Refresh 或观察 Console 无红错) 2. 打开 `Assets/Scripts/Online/IchniOnline.asmdef` 确认 references 包含 BestHTTP GUID Expected Result: 编译零错误,无 warning Evidence: .omo/evidence/task-1-asmdef-refs.png (Editor 无错误截图) ``` **Commit**: YES (group with Tasks 2-4) - Message: `feat(online): add BestHTTP reference to IchniOnline.asmdef` - Files: `Assets/Scripts/Online/IchniOnline.asmdef` - Pre-commit: 验证 Unity 编译通过 --- - [x] 2. ApiResponse.cs — 创建服务端响应模型 **What to do**: - 新建文件: `Assets/Scripts/Online/Network/Models/ApiResponse.cs` - 定义 `ResponseCode` enum(匹配服务端 `Models/Responses/ResponseCode.cs`) - 定义 `GlobalResponse` class(匹配服务端 `GlobalResponse`) - 定义 `ApiResult` 封装类,包含成功/失败状态 + 错误信息 - 使用 `System.Text.Json` 或 `UnityEngine.JsonUtility` 做序列化(优先 JsonUtility 避免额外依赖) - 命名空间: `IchniOnline.Online.Network.Models` **关键映射**: ```csharp // 服务端 ResponseCode public enum ResponseCode { Ok = 10000, BadRequest = 10400, Unauthorized = 10401, Forbidden = 10403, NotFound = 10404, InternalServerError = 10500 } // 服务端 GlobalResponse // 注意: Unity JsonUtility 不支持泛型直接反序列化,需要包装或使用封装层 // 方案: 实现一个非泛型的 ApiResponse 做中间反序列化,再通过 ApiResult 取 Data ``` **Must NOT do**: - 不要引入 Newtonsoft.Json(项目中已无引用) - 不要将序列化逻辑写死到 ApiClient 之外 **Recommended Agent Profile**: - **Category**: `quick` - Reason: 纯数据模型定义,无复杂逻辑 - **Skills**: None **Parallelization**: - **Can Run In Parallel**: YES - **Parallel Group**: Wave 1 (with Tasks 1, 3, 4) - **Blocks**: Tasks 5, 6 - **Blocked By**: None **References**: - `D:\Projects\IchniOnline\IchniOnline.Server\Models\Responses\GlobalResponse.cs` — 服务端 GlobalResponse 实现 - `D:\Projects\IchniOnline\IchniOnline.Server\Models\Responses\ResponseCode.cs` — 服务端 ResponseCode enum **Acceptance Criteria**: - [ ] `ApiResponse.cs` 中定义 `ResponseCode` enum(Ok/BadRequest/Unauthorized/Forbidden/NotFound/InternalServerError) - [ ] `GlobalResponse` class 包含 Code/Message/Data 三个字段 - [ ] Unity 编译通过,无错误 **QA Scenarios**: ``` Scenario: 编译验证 Tool: Unity Editor Preconditions: 文件创建完成,Unity 编译通过 Steps: 1. 在任意脚本中添加 `using IchniOnline.Online.Network.Models;` 2. 使用 `ResponseCode.Ok` 确认 enum 可用 Expected Result: 编译零错误 Evidence: .omo/evidence/task-2-compile.png ``` **Commit**: YES (group with Tasks 1, 3, 4) --- - [x] 3. AuthDtos.cs — 创建认证请求/响应 DTO **What to do**: - 新建文件: `Assets/Scripts/Online/Network/Models/AuthDtos.cs` - 定义以下 DTO(匹配服务端 Models): ```csharp // 请求 DTO [System.Serializable] public class ThirdPartyLoginRequestDto { public string Token; // accessToken.kid public string TokenType; // accessToken.tokenType ("mac") public string MacKey; // accessToken.macKey public string MacAlgorithm; // accessToken.macAlgorithm ("hmac-sha-1") } [System.Serializable] public class LoginRequestDto { public string Username; public string EncryptedPassword; // Base64 of XOR'd bytes public string SessionKey; } [System.Serializable] public class RegisterRequestDto { public string Username; public string Password; public string DisplayName; } // 响应 DTO(与服务端 AuthResponse.cs 对应) [System.Serializable] public class SessionKeyResponseDto { public string sessionKey; public string expiresAt; } [System.Serializable] public class LoginResponseDto { public string Token; // JWT public UserResponseDto User; } [System.Serializable] public class UserResponseDto { public string UserId; public string Username; public string DisplayName; public string AvatarUrl; public int Permission; // 0=Guest, 1=Player, 2=Admin } ``` **Must NOT do**: - 不要包含非认证相关的 DTO(如 Beatmap 相关) **Recommended Agent Profile**: - **Category**: `quick` - Reason: 纯数据结构定义 - **Skills**: None **Parallelization**: - **Can Run In Parallel**: YES - **Parallel Group**: Wave 1 (with Tasks 1, 2, 4) - **Blocks**: Tasks 5, 7 - **Blocked By**: None **References**: - `D:\Projects\IchniOnline\IchniOnline.Server\Models\Requests\ThirdPartyLoginRequest.cs` - `D:\Projects\IchniOnline\IchniOnline.Server\Models\Requests\LoginRequest.cs` - `D:\Projects\IchniOnline\IchniOnline.Server\Models\Requests\RegisterRequest.cs` - `D:\Projects\IchniOnline\IchniOnline.Server\Models\Responses\AuthResponse.cs` **Acceptance Criteria**: - [ ] 所有 DTO 定义为 `[System.Serializable]` - [ ] Unity 编译通过 - [ ] 字段名小写(JSON 反序列化兼容服务端 PascalCase → JsonUtility 需字段名匹配) **QA Scenarios**: ``` Scenario: 编译验证 Tool: Unity Editor Preconditions: 文件创建完成 Steps: Unity 编译自动触发 Expected Result: 无编译错误 Evidence: .omo/evidence/task-3-compile.png ``` **Commit**: YES (group with Tasks 1, 2, 4) --- - [x] 4. LoginCacheData.cs — 扩展 JWT 和服务器用户数据字段 **What to do**: - 编辑 `Assets/Scripts/Online/Models/LoginCacheData.cs` - 添加以下字段: ```csharp public string jwtToken; // JWT Bearer token public string userId; // 服务端返回的 UserId (Guid 字符串) public string displayName; // 服务端返回的 DisplayName public string avatarUrl; // 服务端返回的 AvatarUrl public int permission; // 服务端返回的 Permission public bool hasServerSession; // 标记是否已完成服务端认证 ``` - 更新 `IsValid` 逻辑:`hasServerSession && !string.IsNullOrEmpty(jwtToken)` - 保持向后兼容:构造旧字段保留,无 session 时 `hasServerSession=false` - 添加 `UpdateFromServerResponse(LoginResponseDto response)` 方法 - 添加 `ClearServerSession()` 方法(仅清除 JWT 系列字段,保留 TapTap 原始信息) **Must NOT do**: - 不要删除已有字段 (openId/unionId/name/avatar/email/cacheTimestamp) - 不要破坏 `LoginCacheEditor.cs` 中使用的公开接口 **Recommended Agent Profile**: - **Category**: `quick` - Reason: 小幅度字段扩展,结构简单 - **Skills**: None **Parallelization**: - **Can Run In Parallel**: YES - **Parallel Group**: Wave 1 (with Tasks 1, 2, 3) - **Blocks**: Task 6 - **Blocked By**: None **References**: - `Assets/Scripts/Online/Models/LoginCacheData.cs` — 当前文件 - `D:\Projects\IchniOnline\IchniOnline.Server\Models\Responses\AuthResponse.cs` — 服务端 LoginResponse/UserResponse **Acceptance Criteria**: - [ ] `LoginCacheData` 包含新字段 `jwtToken`, `userId`, `displayName`, `avatarUrl`, `permission`, `hasServerSession` - [ ] 旧 ES3 缓存数据加载后不会报错(缺失字段为默认值,`hasServerSession=false`) - [ ] `IsValid` 在 `hasServerSession` 为 true 时检查 `jwtToken` 非空 - [ ] Unity 编译通过 **QA Scenarios**: ``` Scenario: 向后兼容测试 Tool: Unity Editor + Bash (ES3 操作) Preconditions: LoginCacheData 已修改,Unity 已编译 Steps: 1. 用 LoginCacheEditor.GenerateMockData 写入旧格式数据 2. 读取 LoginCacheManager.CachedData 3. 确认 hasServerSession=false,旧字段值正确 Expected Result: 旧缓存被正确加载,不丢失数据,不报错 Evidence: .omo/evidence/task-4-backward-compat.png ``` **Commit**: YES (group with Tasks 1, 2, 3) --- - [x] 5. ApiClient.cs — BestHTTP 封装单例 **What to do**: - 新建文件: `Assets/Scripts/Online/Network/ApiClient.cs` - 创建 `IchniOnlineApiClient` 类(非 MonoBehaviour 单例,或挂载到 DontDestroyOnLoad 对象) - 核心功能: 1. **Base URL 配置**: `public string BaseUrl { get; set; }`,初始值 `http://localhost:5433` 2. **JWT 管理**: `public string JwtToken { get; set; }`,设置后自动在请求头注入 3. **GET 请求**: `Task> GetAsync(string endpoint)` 4. **POST 请求**: `Task> PostAsync(string endpoint, object body)` 5. **内部实现**: 使用 BestHTTP 的 `HTTPRequest` + `GetHTTPResponseAsync()` - POST body 序列化: `request.RawData = Encoding.UTF8.GetBytes(JsonUtility.ToJson(body))` - Header 设置: `request.SetHeader("Content-Type", "application/json")` - JWT 注入: `request.SetHeader("Authorization", $"Bearer {JwtToken}")` 6. **响应解析**: 从 `resp.DataAsText` 反序列化为 `GlobalResponse`,提取 Data 7. **错误处理**: - HTTP 状态码错误 → ApiResult 含错误信息 - 解析失败 → ApiResult 含异常信息 - 网络超时 → ApiResult.Timeout - 服务器返回失败码 → 按 Code 分类处理 **Error Handling Design**: ```csharp public class ApiResult { public bool IsSuccess; public T Data; public int Code; // ResponseCode public string Message; // 服务端返回的 Message public string ErrorDetail; // 客户端错误详情 public static ApiResult Ok(T data) => ...; public static ApiResult Fail(int code, string msg, string detail = null) => ...; } ``` **Must NOT do**: - 不要阻塞 Unity 主线程(所有请求使用 async/await) - 不要硬编码 URL(BaseUrl 必须可配置) - 不要在 ApiClient 内处理业务逻辑(仅做 HTTP 通信) **Recommended Agent Profile**: - **Category**: `unspecified-high` - Reason: 需要理解 BestHTTP API、异步模式、JSON 序列化,有较多细节 - **Skills**: None (BestHTTP API 文档已通过前期研究覆盖) **Parallelization**: - **Can Run In Parallel**: YES (with Task 6) - **Parallel Group**: Wave 2 (with Task 6) - **Blocks**: Task 7 - **Blocked By**: Tasks 1, 2, 3 **References**: - `Packages/com.tivadar.best.http/Runtime/HTTP/HTTPRequestAsyncExtensions.cs` — BestHTTP async extension methods - `Packages/com.tivadar.best.http/Runtime/HTTP/HTTPRequest.cs` — HTTPRequest class API - `D:\Projects\IchniOnline\IchniOnline.Server\Models\Responses\GlobalResponse.cs` — 服务端响应格式 - `Assets/Scripts/Online/Network/Models/ApiResponse.cs` — 客户端响应模型(同 Wave 1 Task 2) **Acceptance Criteria**: - [ ] `IchniOnlineApiClient` 可配置 `BaseUrl` - [ ] `GetAsync` 发送 GET 请求并正确解析 `GlobalResponse` - [ ] `PostAsync` 发送 POST 请求,body 序列化为 JSON - [ ] JWT Token 自动注入到 Authorization Header - [ ] 网络错误/服务端错误被正确封装为 `ApiResult` - [ ] Unity 编译通过 **QA Scenarios**: ``` Scenario: GET 请求 + 响应解析 Tool: Unity Editor + Bash (可启动测试后端或 mock) Preconditions: IchniOnline 后端在 localhost:5433 运行(或 mock 端点) Steps: 1. 在 Unity Start() 中调用 ApiClient.GetAsync("/api/test/health") 2. 检查 Console 输出请求/响应日志 Expected Result: 请求发送成功,响应被正确解析 Evidence: .omo/evidence/task-5-get-request.png Scenario: JWT 注入验证 Tool: Unity Editor Preconditions: ApiClient.JwtToken 设置为 "test-token" Steps: 1. 发起 PostAsync 请求 2. 检查请求 Header 包含 "Authorization: Bearer test-token" Expected Result: Authorization Header 正确注入 Evidence: .omo/evidence/task-5-jwt-header.png ``` **Commit**: YES (group with Task 6) - Message: `feat(online): implement API client and extend login cache` --- - [x] 6. LoginCacheManager.cs — 扩展 JWT 存取方法 **What to do**: - 编辑 `Assets/Scripts/Online/Logic/LoginCacheManager.cs` - 添加新方法: ```csharp // 保存完整认证会话(JWT + 用户数据) public static void SaveAuthSession(string jwtToken, LoginResponseDto response) // 清除会话(保留 TapTap 原始数据,清除 JWT/服务端数据) public static void ClearSession() // 获取缓存的 JWT Token public static string CachedJwtToken { get; } // 检查是否有有效的服务端会话 public static bool HasValidSession { get; } ``` - ES3 key 保持不变: `Ichni_LoginCache` - `HasCachedLogin` → 改为检查 `HasValidSession` - `CachedData` → 保持返回完整数据 - `SaveFromTapTapAccount` → 保持原有行为(只存 TapTap 数据,不清除已有 JWT) **Must NOT do**: - 不要修改 ES3 key 名称 - 不要改变已有方法的签名 **Recommended Agent Profile**: - **Category**: `quick` - Reason: 简单的方法扩展 - **Skills**: None **Parallelization**: - **Can Run In Parallel**: YES (with Task 5) - **Parallel Group**: Wave 2 (with Task 5) - **Blocks**: Task 7 - **Blocked By**: Tasks 1, 4 **References**: - `Assets/Scripts/Online/Logic/LoginCacheManager.cs` — 当前文件 - `Assets/Scripts/Online/Models/LoginCacheData.cs` — 扩展后的数据模型 **Acceptance Criteria**: - [ ] `SaveAuthSession(jwt, response)` 正确写入 ES3 - [ ] `CachedJwtToken` 从 ES3 读取正确 - [ ] `HasValidSession` 在 jwt 为空或无 session 时返回 false - [ ] 编辑器中 `Ichni/Login Cache` 菜单仍正常工作 - [ ] Unity 编译通过 **QA Scenarios**: ``` Scenario: 保存/读取 JWT 会话 Tool: Unity Editor Preconditions: 已有 LoginCacheData 模型扩展 Steps: 1. LoginCacheManager.SaveAuthSession("test-jwt", mockLoginResponse) 2. 读取 LoginCacheManager.CachedJwtToken 3. 读取 LoginCacheManager.HasValidSession Expected Result: jwt="test-jwt", HasValidSession=true Evidence: .omo/evidence/task-6-save-jwt.png Scenario: 清除会话 Steps: 1. LoginCacheManager.ClearSession() 2. 检查 HasValidSession Expected Result: HasValidSession=false, TapTap 原始数据保留 Evidence: .omo/evidence/task-6-clear.png ``` **Commit**: YES (group with Task 5) --- - [x] 7. AuthService.cs — 认证编排服务 **What to do**: - 新建文件: `Assets/Scripts/Online/Logic/AuthService.cs` - 创建 `IchniOnlineAuthService` 类(非 MonoBehaviour 静态类 或 通过 ThirdPartyServiceManager 实例管理) - 核心接口: ```csharp public static class IchniOnlineAuthService { // 事件 public static event Action OnLoginSuccess; public static event Action OnLoginFailed; // error message public static event Action OnLoginCanceled; // 属性 public static bool IsLoggingIn { get; } public static bool IsLoggedIn { get; } // HasValidSession // TapTap 第三方登录流程 // 1. 调用 ThirdPartyServiceManager.StartTapTapLogin() // 2. 在 OnLoginSuccess 回调中: // a. 从 TapTapAccount 取出 accessToken // b. 构造 ThirdPartyLoginRequestDto // c. 调用 ApiClient.PostAsync("/api/auth/third-party/login", body) // d. 成功 → LoginCacheManager.SaveAuthSession(jwt, response) // e. 失败 → 抛 OnLoginFailed // f. ThirdParty.Unbound → 抛 OnLoginFailed("TapTap account not bound") public static void LoginWithTapTap(); // 密码登录流程 // 1. GET /api/auth/session-key → 得到 sessionKey // 2. 密码 XOR 加密: // byte[] passwordBytes = Encoding.UTF8.GetBytes(password); // byte[] sessionBytes = Encoding.UTF8.GetBytes(sessionKey); // byte[] encrypted = new byte[passwordBytes.Length]; // for (int i = 0; i < passwordBytes.Length; i++) // encrypted[i] = (byte)(passwordBytes[i] ^ sessionBytes[i % sessionBytes.Length]); // string encryptedPassword = Convert.ToBase64String(encrypted); // 3. POST /api/auth/login with { Username, EncryptedPassword, SessionKey } // 4. 成功 → LoginCacheManager.SaveAuthSession(jwt, response) public static async void LoginWithPassword(string username, string password); // 注册流程 // POST /api/auth/register with { Username, Password, DisplayName } // 注意: 注册后不自动返回 JWT,需要用户手动登录 public static async void Register(string username, string password, string displayName); // 登出 public static void Logout(); // 密码 XOR 加密工具方法(可复用) public static string EncryptPassword(string password, string sessionKey); } ``` **Must NOT do**: - 不要直接调用 TapTap SDK 的 LoginWithScopes(由 ThirdPartyServiceManager 封装) - 不要在 AuthService 中管理 UI 状态(只触发事件,由 LoginPage 处理 UI) - 不要硬编码 API 路径(用字符串常量集中管理) **Recommended Agent Profile**: - **Category**: `unspecified-high` - Reason: 涉及多步异步流程编排、事件管理、加密算法实现 - **Skills**: None **Parallelization**: - **Can Run In Parallel**: NO - **Parallel Group**: Wave 3 (sequential) - **Blocks**: Tasks 8, 9, 10 - **Blocked By**: Tasks 5, 6 **References**: - `Assets/Scripts/Online/Logic/ThirdPartyServiceManager.cs` — TapTap SDK 调用封装 - `Assets/Scripts/Online/Network/ApiClient.cs` — HTTP 通信(Task 5) - `Assets/Scripts/Online/Logic/LoginCacheManager.cs` — 缓存管理(Task 6) - `D:\Projects\IchniOnline\IchniOnline.Server\Service\UserService.cs:213-225` — 服务端 XOR 解密算法(客户端需反转实现加密) - `D:\Projects\IchniOnline\IchniOnline.Server\Controller\AuthController.cs` — 服务端 API 端点定义 **Acceptance Criteria**: - [ ] `LoginWithTapTap()` 完整流程: TapTap SDK → API call → JWT → 事件 - [ ] `LoginWithPassword()` 完整流程: session key → XOR 加密 → API call → JWT → 事件 - [ ] `Register()` 调用 POST /api/auth/register - [ ] `Logout()` 清除 JWT + TapTap 登出 - [ ] `EncryptPassword()` 与服务端 `DecryptPassword` 互为逆运算 - [ ] 所有事件正确触发(OnLoginSuccess/OnLoginFailed/OnLoginCanceled) - [ ] Unity 编译通过 **QA Scenarios**: ``` Scenario: 密码 XOR 加密验证 Tool: C# 脚本验证 Preconditions: AuthService.EncryptPassword 已实现 Steps: 1. 设 password="test123", sessionKey="base64encodedkey==" 2. 调用 EncryptPassword 得到 encrypted 3. 用服务端 DecryptPassword 逻辑解密: 取 encrypted Base64 → XOR with sessionKey bytes → 得到明文 Expected Result: 解密后明文等于原始 password Evidence: .omo/evidence/task-7-xor-verify.png Scenario: TapTap 登录流程(模拟) Tool: Unity Editor Play Mode Preconditions: ThirdPartyServiceManager 已初始化 Steps: 1. AuthService.LoginWithTapTap() 2. Console 观察: TapTap SDK 登录 → API POST 请求 → JWT 缓存 Expected Result: 流程完整无异常 Evidence: .omo/evidence/task-7-taaptap-flow.png ``` **Commit**: YES - Message: `feat(online): implement auth service with TapTap/password/register flows` - Files: `Assets/Scripts/Online/Logic/AuthService.cs` - Pre-commit: Unity 编译检查 --- - [x] 8. ThirdPartyServiceManager.cs — 集成 AuthService **What to do**: - 编辑 `Assets/Scripts/Online/Logic/ThirdPartyServiceManager.cs` - 在 `StartTapTapLogin()` 的 `LoginWithScopes` 成功后,**不再直接调用** `LoginCacheManager.SaveFromTapTapAccount(account)`(该操作移至 AuthService 流程中) - 添加事件 `OnTapTapAccessTokenReceived(AccessToken token)` 或修改流程,使 AuthService 能拿到 accessToken - 可选方案 A: ThirdPartyServiceManager 保留纯 SDK 封装,AuthService 通过 `TapTapLogin.Instance.GetCurrentTapAccount()` 获取 account - 可选方案 B: ThirdPartyServiceManager 的 `OnLoginSuccess` 事件中附带 accessToken - **推荐方案 B**: 扩展 `OnLoginSuccess` 事件参数或添加新事件 ```csharp // 新增事件: 携带 accessToken(供 AuthService 使用) public event Action OnLoginWithToken; ``` - `StartTapTapLogin()` 保持不变(依然触发 OnLoginSuccess/OnLoginCanceled/OnLoginFailed) **Must NOT do**: - 不要移除已有的事件(OnLoginSuccess/OnLoginCanceled/OnLoginFailed) - 不要改变 TapTap SDK 初始化逻辑 **Recommended Agent Profile**: - **Category**: `quick` - Reason: 小幅接口扩展 - **Skills**: None **Parallelization**: - **Can Run In Parallel**: YES (with Tasks 9, 10) - **Parallel Group**: Wave 4 (with Tasks 9, 10) - **Blocks**: None - **Blocked By**: Task 7 **References**: - `Assets/Scripts/Online/Logic/ThirdPartyServiceManager.cs` — 当前文件 - `D:\Projects\Open\TapSDKLogin-Unity\Runtime\Public\TapTapAccount.cs` — TapTapAccount 结构(含 accessToken 属性) **Acceptance Criteria**: - [ ] `OnLoginWithToken` 事件在 TapTap 登录成功后触发 - [ ] 事件参数包含完整的 `TapTapAccount` 和 `AccessToken` - [ ] Unity 编译通过 **QA Scenarios**: ``` Scenario: 事件触发测试 Tool: Unity Editor Play Mode Preconditions: ThirdPartyServiceManager 已初始化 Steps: 1. 订阅 ThirdPartyServiceManager.Instance.OnLoginWithToken 2. 调用 StartTapTapLogin() 3. TapTap 登录成功后检查事件是否触发 Expected Result: 事件触发,accessToken.kid/macKey/macAlgorithm 非空 Evidence: .omo/evidence/task-8-event.png ``` **Commit**: YES (group with Tasks 9, 10) - Message: `feat(ui): integrate auth service into login UI` --- - [x] 9. LoginPage.cs — 使用新 AuthService 驱动流程 **What to do**: - 编辑 `Assets/Scripts/UI/LoginPage/LoginPage.cs` - 修改 `OnTapTapClicked()`: - 不再直接调用 `ThirdPartyServiceManager.Instance.StartTapTapLogin()` - 改为调用 `IchniOnlineAuthService.LoginWithTapTap()` - 修改 `OnTapTapLoginSuccess(TapTapAccount account)`: - 不再立即 FadeOut(改为等待 AuthService 的服务端验证完成) - 改为 `IchniOnlineAuthService.OnLoginSuccess += OnApiLoginSuccess` - 添加新回调 `OnApiLoginSuccess(LoginResponseDto response)`: - 服务端验证成功后 FadeOut + 恢复 StartPage - 添加 UI 加载状态: - 显示加载指示器(或按钮禁用+文字变化)在等待服务端响应期间 - 事件订阅: - 订阅 `AuthService.OnLoginSuccess` / `OnLoginFailed` / `OnLoginCanceled` - 替代原有的 ThirdPartyServiceManager 事件(或共存) - 账户密码登录 UI: - 如果 LoginPage 上已有用户名/密码输入框 → 绑定到 `IchniOnlineAuthService.LoginWithPassword()` - 如果没有 → 增加简单的用户名/密码输入框 + 登录按钮 **Must NOT do**: - 不要删除 closeButton 和原有的 UI 结构 - 不要修改 UIPageBase 基类 **Recommended Agent Profile**: - **Category**: `visual-engineering` - Reason: 涉及 UI 逻辑更新和用户交互流程 - **Skills**: None **Parallelization**: - **Can Run In Parallel**: YES (with Tasks 8, 10) - **Parallel Group**: Wave 4 (with Tasks 8, 10) - **Blocks**: None - **Blocked By**: Task 7 **References**: - `Assets/Scripts/UI/LoginPage/LoginPage.cs` — 当前文件 - `Assets/Scripts/Online/Logic/AuthService.cs` — 认证服务(Task 7) - `Assets/Scripts/UI/StartPage/StartUIPage.cs` — StartPage 交互恢复 - `Assets/Scripts/UI/StartPage/StartUIPage.cs:TouchToStart()` — 缓存检测入口 **Acceptance Criteria**: - [ ] TapTap 按钮触发 `AuthService.LoginWithTapTap()` 而非直接调用 ThirdPartyServiceManager - [ ] 登录请求加载状态有 UI 反馈 - [ ] 服务端返回成功后 FadeOut + 恢复 StartPage - [ ] 失败时恢复按钮交互 - [ ] Unity 编译通过 **QA Scenarios**: ``` Scenario: TapTap 登录 UI 流程 Tool: Unity Editor Play Mode Preconditions: LoginPage 在 MenuScene 中打开 Steps: 1. 点击 TapTap 按钮 2. 观察按钮变为非交互状态 3. TapTap SDK 登录窗口出现 4. 完成 TapTap 登录 → 等待服务端验证 5. 成功后 LoginPage 淡出 → 恢复 StartPage Expected Result: UI 流程完整,无中断 Evidence: .omo/evidence/task-9-ui-flow.png ``` **Commit**: YES (group with Tasks 8, 10) --- - [x] 10. StartUIPage.cs — 校验会话有效性 **What to do**: - 编辑 `Assets/Scripts/UI/StartPage/StartUIPage.cs` - `TouchToStart()` 中改用 `LoginCacheManager.HasValidSession` 替代 `LoginCacheManager.HasCachedLogin` - 添加可选的 Token 过期检测: - JWT 本身包含过期时间(可解析 payload 中的 exp 字段) - 如果 JWT 已过期 → 清除 session → 显示 LoginPage - 简单方案: 暂不做 JWT 解码解析,只检查 `HasValidSession` - 如有 `RestoreInteraction()` 方法保持现有逻辑 **Must NOT do**: - 不要修改不相关的 StartUIPage 逻辑 - 不要引入 JWT 解码库(暂不解码 JWT payload) **Recommended Agent Profile**: - **Category**: `visual-engineering` - **Skills**: None **Parallelization**: - **Can Run In Parallel**: YES (with Tasks 8, 9) - **Parallel Group**: Wave 4 (with Tasks 8, 9) - **Blocks**: None - **Blocked By**: Task 7 **References**: - `Assets/Scripts/UI/StartPage/StartUIPage.cs` — 当前文件 - `Assets/Scripts/Online/Logic/LoginCacheManager.cs` — 扩展后的缓存管理器(Task 6) **Acceptance Criteria**: - [ ] `TouchToStart()` 使用 `LoginCacheManager.HasValidSession` 判断 - [ ] 有有效 session → 直接进 ChapterSelection - [ ] 无有效 session → 显示 LoginPage - [ ] Unity 编译通过 **QA Scenarios**: ``` Scenario: 有 JWT session 时跳过登录 Tool: Unity Editor Play Mode Preconditions: ES3 中存在有效 JWT 缓存 Steps: 1. 进入 MenuScene 2. 点击 TouchToStart Expected Result: 直接进入 ChapterSelection,不显示 LoginPage Evidence: .omo/evidence/task-10-skip-login.png Scenario: 无 JWT session 时显示登录 Preconditions: 清除 ES3 缓存 Steps: 1. 进入 MenuScene 2. 点击 TouchToStart Expected Result: LoginPage 显示 Evidence: .omo/evidence/task-10-show-login.png ``` **Commit**: YES (group with Tasks 8, 9) --- ## Final Verification Wave - [x] F1. **Plan Compliance Audit** — `oracle` Read the plan end-to-end. For each "Must Have": verify implementation exists (read file, check compilation). For each "Must NOT Have": search codebase for forbidden patterns — reject with file:line if found. Check evidence files exist in .omo/evidence/. Compare deliverables against plan. Output: `Must Have [N/N] | Must NOT Have [N/N] | Tasks [N/N] | VERDICT: APPROVE/REJECT` - [x] F2. **Code Quality Review** — `unspecified-high` Check compilation status. Review changed files: proper async patterns (no sync-over-async), no `try/catch` swallowing, proper error handling, no magic strings (use constants), correct `[Serializable]` attributes on DTOs. Output: `Build [PASS/FAIL] | Code Quality [N clean/N issues] | VERDICT` - [x] F3. **Real Manual QA** — `unspecified-high` Start from clean scene state. Execute QA scenarios from EVERY task — follow exact steps, capture evidence. Test integration flow: TapTap login → API call → JWT cache → restart → skip login. Test edge cases: network error, invalid credentials, ThirdParty.Unbound. Output: `Scenarios [N/N pass] | Integration [N/N] | Edge Cases [N tested] | VERDICT` - [x] F4. **Scope Fidelity Check** — `deep` For each task: read "What to do", read actual diff (git log/diff). Verify 1:1 — everything in spec was built (no missing), nothing beyond spec was built (no creep). Check "Must NOT do" compliance. Detect cross-task contamination. Output: `Tasks [N/N compliant] | Contamination [CLEAN/N issues] | Unaccounted [CLEAN/N files] | VERDICT` --- ## Commit Strategy - **C1** (Task 1-4): `feat(online): add network models and asmdef references` - **C2** (Task 5-6): `feat(online): implement API client and extend login cache` - **C3** (Task 7): `feat(online): implement auth service with TapTap/password/register flows` - **C4** (Task 8-10): `feat(ui): integrate auth service into login UI` --- ## Success Criteria ### Verification Commands ```bash # In Unity Editor: # 1. Open MenuScene # 2. Enter Play Mode # 3. Click TapTap Login button → should call POST /api/auth/third-party/login # 4. Check Console for network request/response logs # 5. Exit Play Mode → check ES3 cache contains JWT data ``` ### Final Checklist - [ ] No compilation errors in IchniOnline.asmdef or dependent assemblies - [ ] TapTap login → server API call → JWT → ES3 cache - [ ] Password login (if server running) → JWT → ES3 cache - [ ] Register (if server running) → user created - [ ] LoginPage responds to all auth states (loading/success/failure)