Phase 4.A:登录+分享 stub + QQ 改走 URL Scheme 不依 SDK

按用户约定调整 Phase 4 方向:微信继续走 SDK(等项目方提供),QQ 分享
改用 URL Scheme 直接调起 QQ app,**不依赖 SDK**。

调研 daoqi/msext/Class/Utils/QQShareManager.m 发现已有完整的 URL Scheme
fallback 路径:
  - line 14: __has_include(<TencentOpenAPI/QQApiInterface.h>) 条件编译
  - line 231 注释: "Fallback: 使用 URL Scheme (mqqapi://) - 无需 SDK,
    直接调起 (Legacy)"
  - mqqapi://share/to_fri / mqqapi://share/to_qzone / mqqopensdkfriend://share
    等 schema 实现见 line 733/786/789

新增 handler stub(让 H5 启动 / 业务期调登录 / 分享不报 "no handler"):
  - Source/Bridge/Handlers/AccreditLoginHandler.swift
    accreditlogin cb "Response from accreditlogin"
    完整实现需 Phase 4.B 微信 SDK + WeChatManager.authorize() + WeChatAuth.exchangeForUser
    + bridge.call("sharelogin", 7 字段含大写 Province) 反向 callback
  - Source/Bridge/Handlers/FriendsShareHandler.swift
    friendsSharetypeUrlToptitleDescript cb "sharefriend"
    完整实现需 ShareCenter 策略分发 + sharesuccess 反向 callback

WebContainer.registerBridgeHandlers 接入。

文档调整:
  - Plan §2.3 阻塞表:QQ OpenSDK 标"不需要",改 URL Scheme 路径
  - Plan §5 Phase 4 前置:去掉 QQ OpenSDK 一行
  - Plan §5 Phase 4.2:完整改写为 URL Scheme 方案,含 LSApplicationQueriesSchemes
    清单 + URL 构造样例 + "调起即 success" 乐观策略说明
  - Plan §8 进度追踪:4.2 QQ SDK Vendor 标"不需要"
  - Plan §6.4 里程碑加 3 行(前两轮 commit hash 补 + 本次)
  - CLAUDE.md 依赖管理段加 "不接入 QQ SDK" 约定(与"不引入 CocoaPods"
    并列的负面清单)
  - Verification-Checklist Phase 4 章节:E.19/E.20 stub + K/L 待 4.B/4.C
    完整后填充验证项

BuildProject 通过

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
joywayer
2026-06-22 19:19:23 +08:00
co-authored by Claude Opus 4.7
parent 0602b9d4b6
commit aa80aec18c
6 changed files with 108 additions and 11 deletions
+2 -1
View File
@@ -127,7 +127,8 @@ daoqi 仓库当前并存两条工作线:
新项目采用**纯 SPM + Vendor `.xcframework` 两层管理****禁止引入 CocoaPods**(详见 `docs/Development-Plan.md` ADR-006)。
- **首选 SPM**:开源 / 官方提供 Swift Package 的库(Sentry / ZIPFoundation / 七牛 `https://github.com/qiniu/objc-sdk` v8.9.x 等)
- **Vendor `.xcframework` 手动**:所有闭源二进制(微信 / QQ / 高德 AMap / opencore-amr 等),按 Design §14.1「Vendor 接入标准流程」放 `Vendor/<SDKName>/` + Target → General → Frameworks 加入 + `Vendor/<SDKName>/README.md` 记录版本与来源
- **Vendor `.xcframework` 手动**:所有闭源二进制(微信 / 高德 AMap / opencore-amr 等),按 Design §14.1「Vendor 接入标准流程」放 `Vendor/<SDKName>/` + Target → General → Frameworks 加入 + `Vendor/<SDKName>/README.md` 记录版本与来源
- **不接入 QQ SDK**QQ 分享走 URL Scheme 直接调起 QQ app`mqqapi://share/to_fri?...` 等),参 msext `QQShareManager.m` line 14 `__has_include` 条件编译的 fallback 路径。不需要 SDK、不需要 AppID 注册、不需要 handleOpenURL 回调(URL Scheme 单向调起,调起即视为 success)。Info.plist 仅需加 `LSApplicationQueriesSchemes``mqq` / `mqqapi` / `mqqopensdkfriend` 等)
- **禁止 CocoaPods**:项目内不放 `Podfile` / `Podfile.lock` / `Pods/` / `.xcworkspace`。工程入口固定为 `ylgamehall.xcodeproj`
- **新增第三方依赖时**:按 SPM → Vendor 顺序尝试,PR 描述写明选型理由;若发现某 SDK 既无 SPM 又无 XCFramework,回到 ADR-006 决策框架重新评估
+12 -6
View File
@@ -62,7 +62,7 @@ Contract Design Plan(本文档)
| 11 个渠道注入目录的目录名值 | 渠道 / 游戏 ID / 七牛域名 / market 等 | Phase 1 | 项目方/运营 |
| 微信 OpenSDK `.framework` | 登录 / 分享 | Phase 4 | 项目方(可从 msext/Vendor 拷贝) |
| 微信 AppID`wx586a9b321e56efb7`+ Universal Links 配置 | 微信回调 | Phase 4 | 项目方 |
| QQ OpenSDK `.framework` + AppID | QQ 分享 | Phase 4 | 项目方 |
| ~~QQ OpenSDK `.framework` + AppID~~ | ~~QQ 分享~~ | **不需要** — 改用 `mqqapi://` / `mqq://` URL Scheme 直接调起 QQ app(参 msext `QQShareManager.m:14` `__has_include` fallback 路径),无需 SDK 无需 AppID 验证。仅需 Info.plist 加 `LSApplicationQueriesSchemes` (`mqq`/`mqqapi`/`mqqopensdkfriend` 等) | — |
| 后台 `/wechat/login` 中转接口 | secret 不入 IPA 的前提 | Phase 4 | 后台团队 |
| 高德地图 `APIKey` + SDK XCFramework | 定位 | Phase 5 | 已有 key `b0d4a8e3fcbbcc0dd96283b7df6a4494`(但绑死 msext Bundle ID,需用新 Bundle ID 重新申请);SDK 走 Vendor 手动接入(ADR-006),下载页 https://lbs.amap.com/api/ios-location-sdk/download |
| 七牛上传 token 颁发接口 | 录音上传 | Phase 3 | 后台团队 |
@@ -467,7 +467,7 @@ Contract Design Plan(本文档)
#### 前置
- Phase 1 完成
- ⏳ 微信 OpenSDK `.framework` + AppID + Universal Links
- QQ OpenSDK `.framework` + AppID
- ~~QQ OpenSDK~~ **不需要**QQ 分享走 URL Scheme 路径(msext `QQShareManager.m` 已实现的 fallback),无 SDK 无 AppID 验证
- ⏳ 后台 `/wechat/login` 接口(或先用客户端直拼 fallback)
#### 任务清单
@@ -477,8 +477,12 @@ Contract Design Plan(本文档)
- [ ] **4.1** `Vendor/WechatSDK/` 拷贝微信 SDK
- Info.plist 加 URL Scheme`wx586a9b321e56efb7`+ `LSApplicationQueriesSchemes``weixin` / `weixinULAPI` / `weixinURLParamsAPI`
- Entitlement 加 Associated DomainsUniversal Links
- [ ] **4.2** `Vendor/QQShare/` 拷贝 QQ SDK
- Info.plist 加 QQ URL Scheme + `LSApplicationQueriesSchemes``mqq*`
- [ ] **4.2** **QQ 分享走 URL Scheme,不依赖 SDK**(按项目方约定)
- 参 msext `QQShareManager.m:14``__has_include(<TencentOpenAPI/QQApiInterface.h>)`+ `:225-280` URL Scheme fallback 实现
- Info.plist 加 `LSApplicationQueriesSchemes``mqq` / `mqqapi` / `mqqopensdkfriend` / `mqqopensdkapiV2` / `mqqopensdkapiV3` / `mqqopensdkapiV4` / `mqqopensdkdataline` 等(具体见 msext Info.plist
- URL 构造(链接分享):`mqqapi://share/to_fri?` + URL 编码的 `title` / `description` / `url` / `thumbnailURL` / `version=1` / `src_type=app` 等参数
- 不需要 AppID 注册、不需要 QQApi handleOpenURL 回调(URL Scheme 单向调起,无回调)
- `sharesuccess` 反向 callback 无法精确判断分享成功失败(URL Scheme 不返回状态);msext 沿用"调起即视为 success"的乐观策略
- [ ] **4.3** `Source/SDK/WeChat/WeChatSDK.swift` 启动注册(Design §4.2 `registerFull` + 全部 12 个 `MMAPP_SUPPORT_*` flag
- [ ] **4.4** `Source/SDK/WeChat/WeChatManager.swift`@MainActor
- 持久 delegate
@@ -782,7 +786,9 @@ H5 调 `OpenurlTitleData` 打开弹层 WebView,弹层内 H5 用 `window.settin
| 2026-06-22 | **Phase 2.A 8 个大厅 handler 落地** + **Phase 2.B 5 项反向 callback 落地** + BridgeData 加 nonisolated 解 Swift 6 actor 隔离 | `d38ff0f` + `23b3a1f` |
| 2026-06-22 | **业务期 `app_getbattery` / `app_getnetwork` 改为 evaluateJavaScript 重新赋值(不重写文件)**:启动期保持写文件,业务期变化时改为 `webView.evaluateJavaScript("window.app_xxx=N;")` 直接重新赋值(H5 已加载完 `<script src>` 不会再 fetch,重写文件无效)。与 §3.2 反向 callback 双轨同时执行。Design §7.5.2 / §7.5.4 + Contract §4.2 同步 | `4986f36` |
| 2026-06-22 | **Phase 2.C ExternalSubscriptions 生命周期管理**setup/teardown 配对挂 viewWillAppear/viewWillDisappearPhase 6 子游戏 push 时防双发 | `09f071e` |
| 2026-06-22 | 新增 `docs/Verification-Checklist.md` 功能验证清单:累积各 Phase 验证步骤、不在 Phase 进行中频繁验证、完成后统一跑。与 Contract §10 契约边界保持独立 | 本次 commit |
| 2026-06-22 | 新增 `docs/Verification-Checklist.md` 功能验证清单:累积各 Phase 验证步骤、不在 Phase 进行中频繁验证、完成后统一跑。与 Contract §10 契约边界保持独立 | `b2ee1b3` |
| 2026-06-22 | Phase 3.A srcIsloop 本地音频 + 3.B/C/D RemoteAudio stub。AudioPlayer @MainActor 实现 playOnce/loopBackground/stopBackground 三方法与 msext 等价 | `0602b9d` |
| 2026-06-22 | **Phase 4.A 登录+分享 stub + QQ 改走 URL Scheme 不依 SDK**AccreditLoginHandler + FriendsShareHandler stubPlan §2.3 阻塞表去掉 QQ SDKPlan §5 4.2 改为 URL Scheme 路径(参 msext QQShareManager.m fallback);CLAUDE.md 依赖管理段补"不接入 QQ SDK"约定;Verification-Checklist 加 Phase 4 章节 | 本次 commit |
> **§3.4.1 撤销说明**:早期 commit `82bad8a` 实现的 `window.settings.getXxx()` polyfill 已撤销 — Contract §附录 A 自身明示「iOS<9 路径,新外壳如最低系统 ≥ iOS 14 可不实现」,本项目最低 iOS 15.6 → polyfill 路径未启用,H5 用 §7.5 的 `app_*.js` 文件路径作为唯一主路径。详细缘由见 Design §3.4.1(撤销说明)和 CLAUDE.md「典型案例」段。
@@ -868,7 +874,7 @@ H5 调 `OpenurlTitleData` 打开弹层 WebView,弹层内 H5 用 `window.settin
### Phase 4 分享 + 登录
- [ ] 4.1 微信 SDK Vendor + URL Scheme
- [ ] 4.2 QQ SDK Vendor
-~~4.2 QQ SDK Vendor~~ **不需要**QQ 走 URL Scheme,详见 Phase 4.2 任务清单
- [ ] 4.3 WeChatSDK registerFull
- [ ] 4.4 WeChatManagerstate map + FIFO
- [ ] 4.5 SceneDelegate openURL 顺序
+21 -4
View File
@@ -169,11 +169,28 @@ H5 需先用 `bridge.registerHandler('callbackName', fn)` 注册:
---
## Phase 4 — 分享 + 登录(待实现
## Phase 4 — 分享 + 登录(部分实现:4.A stub 完成;4.B/4.C 待外部资源
> Phase 4 实施后填充。预备验证项:
> - accreditlogin 微信授权 + sharelogin 7 字段(含大写 Province
> - friendsSharetypeUrlToptitleDescript 分享微信 / 朋友圈 + sharesuccess
### J. accreditlogin / friendsShare stub4.A 完成)
- [ ] **E.19 accreditlogin** → cb "Response from accreditlogin"stub 仅 cb,无微信授权调起、无 sharelogin 反向)
- [ ] **E.20 friendsSharetypeUrlToptitleDescript** → cb "sharefriend"stub 仅 cb,无微信/QQ 分享调起、无 sharesuccess 反向)
> 4.B 微信 SDK + 后台 /wechat/login + 4.C QQ URL Scheme 完整实现后升级。
### K. 微信 SDK + Universal Links4.B 待实现)
> 项目方提供 microSDK + AppID + UL 后填充。预备验证项:
> - sharelogin 7 字段(含大写 Province + 小写 city 经 danbian: 处理)
> - sharesuccess({success:"2", type:"1"/"2"})
> - app 切到后台微信授权 → 切回 app sharelogin 触发
### L. QQ URL Scheme 分享(4.C 待实现,**不依赖 SDK**
> 移植 msext QQShareManager.m URL Scheme fallback 后填充。预备验证项:
> - 链接分享 → `mqqapi://share/to_fri?` 调起 QQ app
> - 朋友圈分享 → `mqqapi://share/to_qzone?` 调起 QQ 空间
> - 调起即视为 successURL Scheme 单向无返回,msext 同款乐观策略)
---
@@ -0,0 +1,31 @@
//
// AccreditLoginHandler.swift
// ylgamehall
//
// H5 Native handler stubaccreditlogin
// docs/H5-Native-Contract.md §3.1 1 + §3.2 10sharelogin
//
// Phase 4.A stub cb Phase 4.B SDK + AppID +
// Universal Links + /wechat/login fallback
//
// Phase 4.B
// 1. WeChatManager.shared.authorize() WXAuthCodestate UUID
// 2. WeChatAuth.exchangeForUser(code:) /wechat/login
// sns/oauth2/access_token + sns/userinfo
// 3. bridge.call("sharelogin", data: 7 ) callback
// 1:1 Province Pcity danbian:
//
import Foundation
public enum AccreditLoginHandler {
public static func register(on bridge: any BridgeProtocol) {
// 1accreditlogin
// cb: "Response from accreditlogin"
// sharelogin callbackPhase 4.B
bridge.register("accreditlogin") { _, callback in
callback?(.string("Response from accreditlogin"))
}
}
}
@@ -0,0 +1,38 @@
//
// FriendsShareHandler.swift
// ylgamehall
//
// H5 Native handler stubfriendsSharetypeUrlToptitleDescript
// docs/H5-Native-Contract.md §3.1 2 + §3.2 11sharesuccess
//
// Phase 4.A stub cb Phase 4.B SDK + Phase 4.C
// QQ URL Scheme
//
//
// 1. sharefriend (1= / 2=) + sharetype (1= / 2= /
// 3= §3.1 2) + type (1= / 2= / 3=) + url +
// title + description
// 2. ShareCenter.dispatch(req)
// - sharetype = 3 NoopSharePlatform
// - WeChatSharePhase 4.B SDK
// 3. bridge.call("sharesuccess", { success: "2", type: <sharefriend > })
//
// QQ ** SDK** URL Schememqq://share/to_fri
// mqqapi://share/to_qzone daoqi/msext QQShareManager.m fallback
// Phase 4.C
//
import Foundation
public enum FriendsShareHandler {
public static func register(on bridge: any BridgeProtocol) {
// 2friendsSharetypeUrlToptitleDescript
// sharefriend / sharetype / type / url / title / description
// cb: "sharefriend"msext 沿
// sharesuccess callbackPhase 4.B/4.C
bridge.register("friendsSharetypeUrlToptitleDescript") { _, callback in
callback?(.string("sharefriend"))
}
}
}
@@ -71,6 +71,10 @@ public final class WebContainerViewController: UIViewController {
// Phase 3.A + 3.C/3.D stub
LocalAudioHandler.register(on: bridge) // §3.1 3srcIsloop
RemoteAudioHandler.register(on: bridge) // §3.1 45Phase 3.B/3.C/3.D stub
// Phase 4.A + stubPhase 4.B SDK / Phase 4.C QQ URL Scheme
AccreditLoginHandler.register(on: bridge) // §3.1 1Phase 4.B
FriendsShareHandler.register(on: bridge) // §3.1 2Phase 4.B/4.C
}
// MARK: -