Files
CC-Framework.BriskGameServer/Brisk Unity SDK 设计草案.md

601 lines
15 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.
# Brisk Unity SDK 设计草案
本文档用于沉淀当前 Brisk Unity SDK 的首版设计方向,目标是:
- 对开发者极简易用
- 初始化一次后,各模块可直接调用
- 保持内部结构清晰,便于后续扩展
- 优先围绕当前服务端已实现接口设计,不预埋过多未来能力
参考资料:
- `服务参考_临时/Brisk Unity SDK接入文档.md`
- `服务参考_临时/Brisk API接口与示例文档.md`
- `服务参考_临时/Brisk 错误码文档(内部实施版).md`
- `服务参考_临时/openapi.yaml`
- TapTap 官方 Unity 集成与排行榜文档
## 1. 设计目标
首版 SDK 对外遵循以下原则:
- 对外统一使用 `Brisk.xxx`
- 模块能力统一使用 `Brisk.模块.xxx`
- 不暴露底层 HTTP、Envelope、Token 注入等细节
- 初始化参数只保留项目级与设备级信息
- 登录模型统一围绕 `login_provider / login_user_id / code`
- 初始化完成后,模块可直接调用
- 错误码统一在 SDK 内部识别和分发
示例目标用法:
```csharp
await Brisk.InitializeAsync(new BriskOptions
{
BaseUrl = "https://brisk.lightyears.ltd",
GameKey = "demo-game",
ClientVersion = Application.version,
DeviceId = SystemInfo.deviceUniqueIdentifier
});
await Brisk.Auth.LoginWithUserIdAsync("tap", "tap_user_10001");
var me = await Brisk.Player.GetMeAsync();
var top = await Brisk.Leaderboard.GetTopAsync("season-score", 20);
await Brisk.Leaderboard.SubmitScoreAsync("season-score", 128);
```
## 2. 对外 API 结构
首版建议公开如下 API 树:
```csharp
Brisk.InitializeAsync(BriskOptions options)
Brisk.Shutdown()
Brisk.IsInitialized
Brisk.IsLoggedIn
Brisk.AccessToken
Brisk.PlayerId
Brisk.Identity
Brisk.Options
Brisk.Bootstrap
Brisk.Auth.LoginWithUserIdAsync(string loginProvider, string loginUserId, BriskProfile profile = null)
Brisk.Auth.LoginWithCodeAsync(string loginProvider, string code, BriskProfile profile = null)
Brisk.Auth.LogoutAsync()
Brisk.Player.GetMeAsync()
Brisk.Config.GetCurrentAsync()
Brisk.Config.RefreshAsync()
Brisk.Announcements.GetListAsync()
Brisk.Announcements.MarkReadAsync(long id)
Brisk.Leaderboard.GetTopAsync(string rankKey, int limit = 20)
Brisk.Leaderboard.GetMeAsync(string rankKey)
Brisk.Leaderboard.GetAroundMeAsync(string rankKey, int range = 10)
Brisk.Leaderboard.SubmitScoreAsync(string rankKey, long score)
Brisk.Leaderboard.GetCurrentSeasonAsync(string rankKey)
Brisk.Leaderboard.GetSeasonHistoryAsync(string rankKey, int limit = 20)
Brisk.Leaderboard.GetSeasonHistoryDetailAsync(string rankKey, string seasonId, int limit = 20)
Brisk.Archive.GetSlotsAsync()
Brisk.Archive.GetMetaAsync(int slotNo)
Brisk.Archive.UploadAsync(int slotNo, byte[] bytes, int? baseVersion = null, string checksum = null)
Brisk.Archive.DownloadAsync(int slotNo)
Brisk.Space.GetByPlayerIdAsync(string playerId)
Brisk.Space.GetByLoginIdentityAsync(string loginProvider, string loginUserId)
Brisk.Space.GetStatsByPlayerIdAsync(string playerId)
Brisk.Space.GetStatsByLoginIdentityAsync(string loginProvider, string loginUserId)
Brisk.Space.LikeByPlayerIdAsync(string playerId)
Brisk.Space.UnlikeByPlayerIdAsync(string playerId)
Brisk.Space.LikeByLoginIdentityAsync(string loginProvider, string loginUserId)
Brisk.Space.UnlikeByLoginIdentityAsync(string loginProvider, string loginUserId)
Brisk.Space.UpdateMyAsync(object payload)
Brisk.Space.GetMyVisitsAsync()
```
说明:
- `bootstrap` 不单独暴露成模块入口,直接并入 `Brisk.InitializeAsync`
- `Brisk` 根节点只负责全局状态、生命周期、事件和模块入口
- 具体业务能力放在 `Brisk.Auth``Brisk.Leaderboard` 等模块下
## 3. BriskOptions 设计
`BriskOptions` 只保留当前服务端确实需要的参数,并区分必填与推荐字段。
### 3.1 必填字段
根据当前服务端文档,以下字段应为初始化必填:
```csharp
public sealed class BriskOptions
{
public string BaseUrl;
public string GameKey;
public string ClientVersion;
}
```
原因:
- `BaseUrl`
- SDK 所有接口的基础地址
- 推荐直接传 API Base例如 `https://host/api`
- `GameKey`
- `bootstrap``config/current``auth/login/exchange` 都需要
- `ClientVersion`
- 当前服务端接口中为可选,但强烈建议传入
- 会影响最低版本校验与动态配置命中
### 3.2 推荐字段
这些字段不是所有场景必填,但推荐在初始化时一次传入,后续由 SDK 自动复用:
```csharp
public sealed class BriskOptions
{
public string BaseUrl;
public string GameKey;
public string ClientVersion;
public string DeviceId;
public bool EnableLog = false;
public bool ValidateSessionOnInitialize = true;
public IBriskTokenStore TokenStore;
public IBriskErrorPresenter ErrorPresenter;
public Action ExitHandler;
}
```
说明:
- `ClientVersion`
- `bootstrap``config/current``login` 推荐传入
- 后续可用于最低版本校验
- `DeviceId`
- `bootstrap``config/current``login` 推荐传入
- 便于服务端做环境识别与问题排查
- `EnableLog`
- 是否输出 SDK 调试日志
- `ValidateSessionOnInitialize`
- 初始化时如果本地有 token是否自动调用 `player/me` 校验有效性
- `TokenStore`
- 登录态持久化存储接口
- `ErrorPresenter`
- 默认错误弹窗展示接口
- `ExitHandler`
- 遇到需要默认退出的阻断错误时,由宿主游戏接管实际退出逻辑
### 3.3 首版初始化建议
为了保持易用,首版建议如下:
- 调用方最少只需要传 `BaseUrl``GameKey`
- `ClientVersion` 强烈建议传
- `DeviceId` 建议有就传
- 如果不传 `TokenStore`SDK 使用默认本地实现
- 如果不传 `ErrorPresenter`SDK 使用默认实现或仅触发事件
- 如果不传 `ExitHandler`SDK 不直接强制退出,而是交给项目层自行处理
## 4. 初始化生命周期
`Brisk.InitializeAsync` 的建议流程如下:
### 4.1 标准流程
```txt
1. 校验 BriskOptions
2. 规范化 BaseUrl
3. 初始化 Core 运行时对象
4. 调用 GET /client/bootstrap
5. 缓存 bootstrap 结果
6. 根据 bootstrap 检查维护状态与最低版本
7. 初始化各模块入口
8. 从 TokenStore 恢复本地 token
9. 如果配置了 ValidateSessionOnInitialize 且存在 token则调用 GET /player/me 验证会话
10. 进入可用状态
```
### 4.2 具体建议
- `BaseUrl` 允许调用方传:
- `https://host/api`
-`https://host`
- SDK 内部统一规范化为最终 API Base
- 初始化阶段不依赖 `login_provider / login_user_id`
- `bootstrap` 结果建议缓存到:
- `Brisk.Bootstrap`
- 如果初始化阶段检测到以下情况,直接触发全局阻断错误:
- 项目维护中
- 最低版本不满足
- 本地恢复到的账号已失效或已封禁
### 4.3 不建议的行为
首版不建议做以下事情:
- 不自动静默重新登录
- 当前服务端没有 refresh token 机制
- 不在初始化中自动执行第三方平台登录
- 第三方登录由游戏侧完成Brisk 只负责兑换 Brisk 会话
- 不把所有模块的预加载都塞进初始化
- 公告、排行榜、空间等业务数据由调用方按需拉取
## 5. 登录态设计
首版登录核心模型:
```txt
login_provider + login_user_id -> POST /auth/login/exchange -> Brisk access_token
login_provider + code -> POST /auth/login/exchange -> Brisk access_token
```
身份建议分为两层:
- 外部身份
- `login_provider`
- `login_user_id`
- 内部身份
- `player_id`
- `project_account_id`
推荐登录行为:
```txt
1. 游戏先完成第三方平台登录
2. 游戏拿到 `login_provider`
3. 再拿到 `login_user_id` 或 `code`
4. 调用 `Brisk.Auth.LoginWithUserIdAsync(...)` 或 `Brisk.Auth.LoginWithCodeAsync(...)`
5. SDK 保存 token、过期时间、player_id、project_account_id
6. 后续玩家态接口自动附带 Bearer Token
```
## 6. 错误处理总设计
首版错误处理原则改为:
- SDK 只统一接管严重阻断错误
- 普通接口错误不走全局监听
- 普通接口错误由调用方在调用处自行处理
- SDK 默认提供一套阻断错误弹窗 UI
- 开发者可以替换默认 UI 或自行接管严重错误
内部处理链路建议如下:
```txt
BriskHttpClient
-> BriskResponseParser
-> BriskErrorClassifier
-> 严重错误进入 SDK 全局处理
-> 普通错误直接返回给当前调用方
```
职责划分:
- `BriskHttpClient`
- 发请求、收响应、拿到 HTTP 状态码
- `BriskResponseParser`
- 解析 `code/message/data`
- `BriskErrorClassifier`
- 把错误码映射成 SDK 语义错误
- 严重错误全局处理
- 只处理维护、封号、登录态失效等阻断错误
- 普通错误调用点处理
- 由调用方 `try/catch`
- 或由调用方传入当前接口自己的回调处理
## 7. 错误分级
首版建议将错误分为以下几类:
```csharp
public enum BriskErrorKind
{
Retryable,
Business,
AuthExpired,
GlobalBlocking,
Fatal
}
```
说明:
- `Retryable`
- 超时、网络抖动、429、临时服务不可用
- `Business`
- 普通业务失败,例如存档冲突、参数不合法
- 不进入 SDK 全局错误处理
- `AuthExpired`
- 登录态失效,需要清理会话
- 属于 SDK 全局接管范围
- `GlobalBlocking`
- 封号、维护中、强制更新等,需要阻断当前玩家继续操作
- 属于 SDK 全局接管范围
- `Fatal`
- SDK 未初始化、关键配置错误、内部不可恢复异常
- 只用于真正不可恢复的框架级错误
## 8. 错误码映射建议
根据当前错误码文档,首版建议如下处理:
### 8.1 AuthExpired
- `10001` 缺少 token
- `10002` token 无效
- `10023` 缺少会话
默认行为:
- 清空本地 token
- 更新 `Brisk.IsLoggedIn = false`
- 触发 `Brisk.OnAuthExpired`
- 默认弹出登录态失效提示 UI
- 抛出 `BriskAuthExpiredException`
### 8.2 GlobalBlocking
- `10003` 项目维护中
- `10004` 玩家已封禁
- `10005` 访问校验失败
- `10025` 项目维护中
- `10026` 玩家已封禁
默认行为:
- 触发 `Brisk.OnBlockingError`
- 使用 SDK 默认 UI 弹出阻断提示
- 如果开发者自定义了 `ErrorPresenter`,则由开发者 UI 接管展示
- 是否退出程序由 `ExitHandler` 决定,不在网络底层写死
进一步建议:
- `10003``10025` -> `Maintenance`
- `10004``10026` -> `AccountBanned`
- `10005` -> `AccessDenied`
### 8.3 Retryable
- `80001` 请求过于频繁
- `80002` 限流服务不可用
默认行为:
- 短退避重试
- 超过最大次数后抛出异常
- 不触发全局 UI
### 8.4 Business
- `60016` 存档版本冲突
- `60017` 校验值不匹配
- `60018` 存档超出大小限制
- `60021` 存档不存在
- `70011` 玩家不存在
- `70017` 玩家不存在
- `70020` 玩家不存在
默认行为:
- 不做系统级强拦截
- 不走全局错误监听
- 抛出对应业务异常,由调用方决定如何提示
- 如果未来个别接口希望提供便利用法,可在该接口额外支持传入当前调用专属回调
## 9. 异常类命名建议
首版建议使用以下异常层次:
```csharp
BriskException
BriskNetworkException
BriskHttpException
BriskApiException
BriskBusinessException
BriskAuthExpiredException
BriskBlockingException
BriskMaintenanceException
BriskAccountBannedException
BriskRateLimitException
BriskArchiveConflictException
BriskArchiveChecksumException
BriskArchiveTooLargeException
BriskNotInitializedException
```
说明:
- `BriskException`
- SDK 所有异常的统一基类
- `BriskApiException`
- 已成功收到接口响应,但 `code != 0`
- `BriskBusinessException`
- 普通业务错误
- `BriskAuthExpiredException`
- 登录态失效
- `BriskBlockingException`
- 全局阻断错误的公共基类
- `BriskMaintenanceException`
- 维护中
- `BriskAccountBannedException`
- 账号封禁
- `BriskNotInitializedException`
- 未初始化即调用模块接口
## 10. 全局事件命名建议
首版建议 `Brisk` 根节点提供以下全局事件:
```csharp
Brisk.OnInitialized
Brisk.OnLoggedIn
Brisk.OnLoggedOut
Brisk.OnAuthExpired
Brisk.OnBlockingError
```
建议语义:
- `OnInitialized`
- SDK 初始化完成
- `OnLoggedIn`
- 登录成功
- `OnLoggedOut`
- 主动登出完成
- `OnAuthExpired`
- 登录态失效
- `OnBlockingError`
- 维护、封号、访问阻断等全局错误
可选接管接口:
```csharp
Brisk.SetErrorPresenter(IBriskErrorPresenter presenter)
Brisk.SetExitHandler(Action exitHandler)
```
## 11. 关于“底层直接弹窗并退出程序”的建议
这个方向本身是合理的,但不建议把“弹窗”和“退出程序”都硬编码在网络底层。
更推荐的做法是:
```txt
网络底层识别错误
-> 严重错误交给 SDK 核心处理
-> 默认错误展示器弹窗
-> 是否退出由宿主游戏或 ExitHandler 决定
```
这样做的好处:
- 设计更常规
- 便于项目方接管 UI 风格
- 便于区分“该回登录页”还是“该退出程序”
- 避免网络层和游戏表现层耦合过深
- 避免普通接口错误被 SDK 过度吞掉或误处理
具体建议:
- `登录态失效`
- 默认不退出程序
- 默认弹窗提示后清会话并回登录流程
- `账号封禁`
- 默认弹窗提示后由项目方决定回登录页或退出
- `维护中`
- 默认阻断流程并提示
- `Fatal`
- 可以作为最接近“提示后退出”的类型
## 12. 可选扩展:标准模式与弱网模式
这项需求合理,但优先级建议放在首版正常逻辑之后。
后续可以考虑增加:
```csharp
public enum BriskNetworkMode
{
Standard,
WeakNetwork
}
```
两者差异可以主要体现在:
- 重试次数
- 超时时间
- 429 退避策略
- 是否对部分只读接口自动重试
建议首版先只做:
- 一个默认标准策略
- 策略入口预留在 `BriskOptions`
例如:
```csharp
public sealed class BriskOptions
{
public BriskNetworkMode NetworkMode = BriskNetworkMode.Standard;
}
```
但首版实现只落 `Standard` 即可。
## 13. 首版内部结构建议
```txt
Assets/
BriskSdk/
Runtime/
Core/
Brisk.cs
BriskOptions.cs
BriskContext.cs
BriskSession.cs
BriskHttpClient.cs
BriskModuleExecutor.cs
BriskErrorClassifier.cs
Auth/
Player/
Config/
Announcement/
Leaderboard/
Archive/
Space/
Models/
Storage/
Exceptions/
Samples/
QuickStart/
Scenes/
BriskQuickStartScene.unity
PackageSource/
com.foldcc.cc-framework.BriskGameServer/
package.json
README.md
CHANGELOG.md
Documentation~/
```
对外尽量只暴露:
- `Brisk`
- `BriskOptions`
- 各模块入口类
- 结果数据结构
- 必要异常类
## 14. 当前阶段结论
当前已明确的设计结论:
- 对外统一使用 `Brisk.xxx`
- 初始化完成后各模块直接可用
- 初始化参数不再包含 `channel / platform`
- 登录模型统一为 `login_provider / login_user_id / code`
- `BriskOptions` 最少只要求 `BaseUrl``GameKey`
- `ClientVersion` 为可选但强烈建议传入
- 网络底层负责识别错误,不直接写死 UI 和退出行为
- SDK 核心负责全局错误策略、全局事件和默认表现
- “弱网模式”作为后续扩展项预留,不阻塞首版实现
## 15. 下一步建议
建议按以下顺序继续推进:
1. 先搭 SDK 骨架与目录结构
2. 先实现 `Brisk.InitializeAsync`
3. 先打通 `Auth``Player``Leaderboard`
4. 再接入 `Archive` 的二进制上传下载
5. 最后补充全局错误展示器与默认本地存储实现