ADR-009 凭证集中化:AppSecrets.plist + 七牛运行参数走 RemoteConfig.audio_*(契约影响)

【数据流改造(三分制)】
- 微信 AppID  唯一源 = Info.plist CFBundleURLTypes (URLName=weixin first scheme)
- 应用级凭证 唯一源 = AppSecrets.plist (wxAppSecret/qiniuAccessKey/qiniuSecretKey)
- 七牛运行参数 唯一源 = RemoteConfig 顶层 audio_domain / audio_bucket(远端动态注入)

【契约影响】
- ChannelConfig.plist:11 key → 10 key,移除 qiniudomain(ADR-007 守护规则同步)
- BundleConfig:删除 qiniuDomain 属性
- RemoteConfig:顶层新增可选字段 audioDomain / audioBucket(JSON snake_case 自动归一化)
- 启动期:WebContainerViewController parsed 分支校验 audio_domain/audio_bucket 非空,
  缺失抛 BootError.audioConfigMissing 弹 modal 永停(与 showmessage 同等致命)
- 上线前置:测试 / 生产远端 .txt 配置必须先补 audio_domain / audio_bucket 两个顶层 key
- WeChatSDK.appID / WeChatAuth.appSecret / QiniuConfig.* 调用方零签名变化

【新增】
- ylgamehall/Resources/AppSecrets.plist(3 key)
- ylgamehall/Source/Resource/AppSecrets.swift(单例加载,对齐 BundleConfig 模式)
- QiniuConfig 改 actor:cdnDomain/bucketName 进 actor 状态 + update(...) async;
  accessKey/secretKey 仍 nonisolated(直接读 AppSecrets)
- QiniuTokenSigner.uploadToken() 改 async(bucketName 来自 actor)
- QiniuUploader 预取 cdnDomain 闭包外,SDK 同步 callback 内直接拼 URL

【删除】
- WeChatSDK.swift  static let appID  硬编码 → Info.plist 启动期解析
- WeChatAuth.swift static let appSecret 硬编码 → AppSecrets.shared.wxAppSecret
- QiniuConfig 中 accessKey / secretKey / bucketName / cdnDomain 四处硬编码
- ChannelConfig.plist 的 qiniudomain 字段(plist 与代码双源僵尸字段)

【文档同步】
- Plan:新增 ADR-009 + ADR-007 守护规则改 10 key + §236 BundleConfig 描述
- Design §3.4 多处 "11 项" → "10 项";§7.0.3 plist 示例 + BundleConfig 代码骨架
  + Scripts/inject_channel.sh 同步
- SDK-Integration-Guide §0 凭证位置改三分制 + §尾"七牛域名读取"加新外壳路径
- Verification-Checklist L69 "(11 项)" → "(10 项)"

参考契约章节:docs/Development-Plan.md ADR-009、docs/H5-Native-Implementation-Design.md §7.0.3
BuildProject 通过,Xcode 即时诊断 0 警告。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
joywayer
2026-06-27 21:07:55 +08:00
co-authored by Claude Opus 4.7
parent 8125dd4d11
commit 83d9502dee
15 changed files with 260 additions and 67 deletions
+40 -8
View File
@@ -233,8 +233,8 @@ Contract Design Plan(本文档)
- [x] **1.1** 创建 `ylgamehall/Resources/ChannelConfig.plist` 含 11 个渠道键值对(沿用 msext 现网 demo 作为母包默认值;ADR-007 决策)
- [x] **1.2** 实现 `ylgamehall/Source/Resource/BundleConfig.swift`
- `init(bundle: Bundle = .main)`:读 `bundle.url(forResource: "ChannelConfig", withExtension: "plist")`,用 `PropertyListSerialization` 反序列化为 `[String: String]`
- 公开 11 个只读属性:`qiniuDomain` / `gameId` / `channel` / `gameDir` / `gameStart` / `gameConfig` / `market` / `agent` / `appVersion` / `other` / `appleConfig`
- 单测 fixture:建立 mock bundle 含 fixture plist,验证 11 个 key 全部能读出且缺失 key 返回空串
- 公开 10 个只读属性:`gameId` / `channel` / `gameDir` / `gameStart` / `gameConfig` / `market` / `agent` / `appVersion` / `other` / `appleConfig``qiniuDomain` 已于 2026-06-27 移除,七牛 CDN 改 RemoteConfig.audio_domain 远端注入,详见 ADR-009
- 单测 fixture:建立 mock bundle 含 fixture plist,验证 10 个 key 全部能读出且缺失 key 返回空串
- [x] **1.3** 实现 `ylgamehall/Source/Resource/SandboxPaths.swift`
- 常量:`caches` / `documents` / `bundle`
- `lobbyIndex() -> URL` 拼出 `{Caches}/{gamedir}/{gamestart}/index.html`
@@ -1018,7 +1018,8 @@ H5 调 `OpenurlTitleData` 打开弹层 WebView,弹层内 H5 用 `window.settin
- **修订决策**:废止"仓库根 `Resources/`"的固定结构假设,**项目内资源目录由各 Phase 实施时按需落地**:
- 静态 Bundle 资源 → `ylgamehall/Resources/`synchronized group 自动收集,含 `gamehall.zip` / `ChannelConfig.plist` 等)
- 原生 Asset Catalog → `ylgamehall/Assets.xcassets/`
- 渠道注入值 → `ylgamehall/Resources/ChannelConfig.plist`11 个 string key,母包默认值;ADR-007
- 渠道注入值 → `ylgamehall/Resources/ChannelConfig.plist`10 个 string key,母包默认值;ADR-007、ADR-009
- 应用级凭证 → `ylgamehall/Resources/AppSecrets.plist`3 个 string keywxAppSecret / qiniuAccessKey / qiniuSecretKeyADR-009
- 闭源 SDK 二进制 → `Vendor/<SDK>/<SDK>.xcframework`
- 私人原始素材池 → `docs/res/`(项目不感知,需要时拷贝到上述工程内目录)
- **理由**
@@ -1079,12 +1080,12 @@ H5 调 `OpenurlTitleData` 打开弹层 WebView,弹层内 H5 用 `window.settin
1. 默认开启的 User Script Sandboxing 拒绝读 `Scripts/copy_channel_injection.sh``ChannelInjection/`
2. 即使关掉 sandboxRun Script 在没有 Input/Output 声明 + 默认勾选"Based on dependency analysis"时,被 Xcode 视为"无依赖故无需运行"clean build 也不跑
- 综合工程成本估算:为保留"目录名编码"机制,需引入额外 Build Phase / 关闭 sandbox / 维护 Input/Output 列表,**全是 Xcode 行为兼容性维护,与项目目标无关**
- **决策**:渠道注入存储改用**单一 `ylgamehall/Resources/ChannelConfig.plist`** 含 11 个 string key,与 msext 11 个目录一一对应
- **决策**:渠道注入存储改用**单一 `ylgamehall/Resources/ChannelConfig.plist`** 含 10 个 string key2026-06-22 初版 11 个,2026-06-27 移除 `qiniudomain`,详见 ADR-009
- 存储介质:plist(iOS 原生)
- Bundle 加载:synchronized group 自动收集,零配置
- 运行时读取:`BundleConfig.init(bundle:)``PropertyListSerialization` 反序列化
- **理由**
- **契约 100% 等价**H5 端通过 `app_data.js` 看到的 11 个 JS 全局变量行为完全不变(契约边界在 `BundleConfig.shared.xxx`,与底层存储无关)
- **契约 100% 等价**H5 端通过 `app_data.js` 看到的 JS 全局变量行为完全不变(契约边界在 `BundleConfig.shared.xxx`,与底层存储无关)
- **CLAUDE.md 原则 B 落地**:原生内部自由重构,不要照搬旧项目;msext 那套是 iOS 9 / Xcode 14 时代的 hackXcode 26 + Swift 6 应当用更现代的存储
- **IPA 后处理多渠道分发完全等价**:
```bash
@@ -1099,9 +1100,11 @@ H5 调 `OpenurlTitleData` 打开弹层 WebView,弹层内 H5 用 `window.settin
- Contract §0.3 描述的 msext 实现仅作为历史参考,新项目不照搬
- Design §7.0 / §7.2 重写为 plist 方案
- Plan Phase 1.1 简化为"建 plist"单步任务(取代原 1.1.a~d 四步 inject_channel.sh 方案)
- `BundleConfig.swift` 接口(11 个公开属性)保持不变,仅 init 实现切换
- `BundleConfig.swift` 接口(公开属性,当前 10 个)保持稳定,仅 init 实现切换
- **守护**
- **`ChannelConfig.plist` 必须保持 11 个 key 完整且类型为 string**;新增 key 视同契约边界变更,需更新 Design / Plan / Contract(如该值被 H5 通过 app_data.js 暴露)
- **`ChannelConfig.plist` 必须保持 10 个 key 完整且类型为 string**gameid/channel/gamedir/gamestart/gameconfig/market/agent/appversion/other/appleconfig);新增 / 移除 key 视同契约边界变更,需更新 Design / Plan / Contract(如该值被 H5 通过 app_data.js 暴露)
- 跨渠道相同的应用凭证(微信 AppSecret、七牛 AccessKey/SecretKey)放 `AppSecrets.plist`,不进 ChannelConfigADR-009
- 七牛 CDN 域名、bucket 名走 RemoteConfig 顶层 `audio_domain` / `audio_bucket` 远端注入,不在任何本地 plistADR-009
- 后处理工具必须**修改 plist 后立即重签**,否则 iOS 拒绝安装
- 不允许把渠道值硬编码到 Swift 源码(违背"母包模式"的初衷:一份二进制 N 个渠道)
- **回滚条件**:若未来 Xcode / iOS 改动让 plist 方案无法工作(极不可能)或后处理脚本失效,重新评估目录名方案或其它存储介质
@@ -1243,5 +1246,34 @@ ResolvedVersion(
---
### ADR-009:凭证 / 七牛运行参数三分制(2026-06-27)
- **背景**:本次修订前,微信 AppID/Secret + 七牛 AccessKey/SecretKey/bucket/CDN 域名分散在 3 处:
- Swift 源码硬编码:`WeChatSDK.swift`(AppID) / `WeChatAuth.swift`(AppSecret) / `QiniuConfig.swift`(AccessKey/SecretKey/bucket/CDN)
- `Info.plist` 的 CFBundleURLTypes:微信 AppID 又出现一次(iOS 系统级 URL Scheme,必须在 Info.plist
- `ChannelConfig.plist` 的 `qiniudomain`:CDN 域名又一份,但 Swift 代码完全没读取(双源僵尸字段)
- **问题**:无单一真相源 → 多渠道分发 / 后台改后台时容易遗漏某处;CDN 域名出现"plist 改了但代码用硬编码"的潜在 bug
- **决策(三分制)**
| 数据维度 | 唯一权威源 | 理由 |
|---|---|---|
| 微信 AppID | `Info.plist` 的 CFBundleURLTypes (URLName=weixin 首个 scheme) | iOS 系统级 URL Scheme 注册,运行期不可注入,Info.plist 是事实唯一可写位置 |
| 应用级凭证(微信 AppSecret、七牛 AccessKey、七牛 SecretKey | `AppSecrets.plist` | 跨渠道相同的应用全局凭证;与渠道差异化字段语义分离 |
| 七牛 CDN 域名、bucket 名 | RemoteConfig 顶层 `audio_domain` / `audio_bucket` | 后台运维管理,无客户端直接出 ad-hoc 改值的诉求 |
- **远端注入时序**`WebContainerViewController` 在 `parsed` 分支(即 RemoteConfig 拉到、IPA 校验前)即调 `await QiniuConfig.shared.update(cdnDomain:bucketName:)` 注入;缺失 audio_domain / audio_bucket 抛 `BootError.audioConfigMissing`,与 showmessage 同等致命,弹 modal 永停(理由:缺这俩 → 录音上传必失败,业务不可用,启动期即报远比运行期某次上传时报更友好)
- **类型设计**
- `AppSecrets`:与 `BundleConfig` 同款 `nonisolated public final class Sendable`3 个不可变 String 属性
- `QiniuConfig`:从 `enum` 改为 `actor``accessKey`/`secretKey` 仍 nonisolated(直接读 `AppSecrets.shared`),`cdnDomain`/`bucketName` 进 actor 状态;`update(...)` / `publicURL(...)` async
- `QiniuTokenSigner.uploadToken()` 改 async(因 bucketName 来自 actor
- `QiniuUploader.upload()` 预取 cdnDomain 一次(避免 SDK 同步 callback 内再 await
- **删除清单**
- `WeChatSDK.swift` `static let appID = "..."` 硬编码 → 改读 Info.plist
- `WeChatAuth.swift` `static let appSecret = "..."` → 改读 `AppSecrets.shared.wxAppSecret`
- `QiniuConfig.swift` `static let accessKey/secretKey/bucketName/cdnDomain = "..."` 四处硬编码
- `ChannelConfig.plist` `<key>qiniudomain</key>` 字段
- `BundleConfig.swift` `qiniuDomain` 属性(10 key 守护规则同步)
- **回滚条件**:若未来 `Info.plist` 不再允许动态读取 CFBundleURLTypes(极不可能),或 RemoteConfig 接口被替换为按渠道差异化下发(需要进 4 层 fallback),重新评估
---
文档完成日期:2026-06-21
最后更新:2026-06-27ADR-008 第三轮修订:VersionResolver 单链 4 层 fallback + 5 字段共用同一查找接口;前置历史 2026-06-22 ADR-008 二次精确化 / ADR-007 渠道注入改 ChannelConfig.plist / ADR-006 纯 SPM + Vendor / ADR-005 极光降级 / Resources 目录记录)
最后更新:2026-06-27ADR-009 凭证集中化 + AppSecrets.plist + 七牛运行参数远端注入 + ChannelConfig 11→10 key;同日 ADR-008 第三轮修订:VersionResolver 单链 4 层 fallback;前置历史 2026-06-22 ADR-008 二次精确化 / ADR-007 渠道注入改 ChannelConfig.plist / ADR-006 纯 SPM + Vendor / ADR-005 极光降级 / Resources 目录记录)