# 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. 最后补充全局错误展示器与默认本地存储实现