Files
ichni_Official/.omo/plans/online-api-integration.md
Developer ebd5dafa2d update
2026-06-18 10:06:49 +08:00

919 lines
36 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<T>`
- **TapTap SDK** 登录返回 `TapTapAccount.accessToken`kid/tokenType/macKey/macAlgorithm可直接映射到 `ThirdPartyLoginRequest`
- **服务端密码加密**: XOR with session key`UserService.DecryptPassword`
- **服务端响应格式**: `GlobalResponse<T> { 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<T>` 完全匹配
- 密码 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<T> 响应模型 [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<T>` class匹配服务端 `GlobalResponse<T>`
- 定义 `ApiResult<T>` 封装类,包含成功/失败状态 + 错误信息
- 使用 `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<T>
// 注意: Unity JsonUtility 不支持泛型直接反序列化,需要包装或使用封装层
// 方案: 实现一个非泛型的 ApiResponse 做中间反序列化,再通过 ApiResult<T> 取 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` enumOk/BadRequest/Unauthorized/Forbidden/NotFound/InternalServerError
- [ ] `GlobalResponse<T>` 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<ApiResult<T>> GetAsync<T>(string endpoint)`
4. **POST 请求**: `Task<ApiResult<T>> PostAsync<T>(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<T>`,提取 Data
7. **错误处理**:
- HTTP 状态码错误 → ApiResult 含错误信息
- 解析失败 → ApiResult 含异常信息
- 网络超时 → ApiResult.Timeout
- 服务器返回失败码 → 按 Code 分类处理
**Error Handling Design**:
```csharp
public class ApiResult<T> {
public bool IsSuccess;
public T Data;
public int Code; // ResponseCode
public string Message; // 服务端返回的 Message
public string ErrorDetail; // 客户端错误详情
public static ApiResult<T> Ok(T data) => ...;
public static ApiResult<T> Fail(int code, string msg, string detail = null) => ...;
}
```
**Must NOT do**:
- 不要阻塞 Unity 主线程(所有请求使用 async/await
- 不要硬编码 URLBaseUrl 必须可配置)
- 不要在 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<T>` 发送 GET 请求并正确解析 `GlobalResponse<T>`
- [ ] `PostAsync<T>` 发送 POST 请求body 序列化为 JSON
- [ ] JWT Token 自动注入到 Authorization Header
- [ ] 网络错误/服务端错误被正确封装为 `ApiResult<T>`
- [ ] Unity 编译通过
**QA Scenarios**:
```
Scenario: GET 请求 + 响应解析
Tool: Unity Editor + Bash (可启动测试后端或 mock)
Preconditions: IchniOnline 后端在 localhost:5433 运行(或 mock 端点)
Steps:
1. 在 Unity Start() 中调用 ApiClient.GetAsync<object>("/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<LoginResponseDto> OnLoginSuccess;
public static event Action<string> 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<LoginResponseDto>("/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<TapTapAccount, AccessToken> 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)