You've already forked CC-Framework.BriskGameServer
601 lines
15 KiB
Markdown
601 lines
15 KiB
Markdown
# 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. 最后补充全局错误展示器与默认本地存储实现
|