From d9d9554d9f794007a5a66ad812d543dff5c2ea34 Mon Sep 17 00:00:00 2001 From: joywayer Date: Tue, 23 Jun 2026 01:33:05 +0800 Subject: [PATCH] =?UTF-8?q?Plan=20=E8=BF=9B=E5=BA=A6=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=EF=BC=9APhase=203.6=20/=204.1=20/=204.3-4.10=20/=205.1-5.6=20?= =?UTF-8?q?=E5=8B=BE=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 按 CLAUDE.md「进度同步规范」要求,把已落地的子项在 Development-Plan.md §5 / §8 中同步勾选。同时修正几处描述与当前实现的差异: - 4.1 微信 SDK:xcframework 2.0.5 入库 Vendor(不是 Embed Frameworks), Universal Links 不接(企业签分发不需要) - 4.3 WeChatSDK:新 SDK 2.x 无 MMAPP_SUPPORT_* flag,简单 registerApp - 4.6 WeChatAuth:客户端直拼 sns/oauth2/access_token + sns/userinfo, 接受 msext 同款 secret 路径(不走后台 /wechat/login) - 5.1 AMap:高德官方不出 xcframework,回退 fat .framework,M Mac 模拟器 链接失败是接受的现状(详见 Vendor/AMap/README.md「已知限制」) - 5.3 AMapWrapper:updatePrivacyShow/Agree 是 AMapLocationManager 类方法 - 5.4 LocationService:仅 requestOnce + stop,未实现持续模式(msext 也 只是单次,H5 未要求持续) - 3.6 Qiniu:CryptoKit HMAC-SHA1 自签 token + QNUploadManager 走 initWithConfiguration + defaultConfigurationV2 Phase 4.11 / 4.12(截图 / 远端图分享)下个 commit 实现。 Co-Authored-By: Claude Opus 4.7 --- docs/Development-Plan.md | 111 ++++++++++++++++++++------------------- 1 file changed, 56 insertions(+), 55 deletions(-) diff --git a/docs/Development-Plan.md b/docs/Development-Plan.md index da1c020..41b71de 100644 --- a/docs/Development-Plan.md +++ b/docs/Development-Plan.md @@ -433,9 +433,11 @@ Contract Design Plan(本文档) - [ ] **3.5** `Source/Audio/AudioRecorder.swift`(actor) - `record() async throws -> AudioFile`:`AVAudioRecorder` 录 WAV - 麦克风权限:Info.plist 加 `NSMicrophoneUsageDescription = "{gamehallname}需要访问您的麦克风录制语音消息"` -- [ ] **3.6** 七牛 SPM 依赖(v8+)+ `Source/Network/QiniuUploader.swift` - - `upload(_:token:) async throws -> UploadedFile` - - 单测:mock token,模拟上传成功 / 失败 +- [x] **3.6** 七牛 SPM 依赖(`https://github.com/qiniu/objc-sdk` v8.9.2,传递依赖 happy-dns 1.0.4 自动解析)+ 客户端自签 token 路径 + - `Source/Network/QiniuConfig.swift`:4 项常量(AccessKey / SecretKey / BucketName / cdnDomain)+ publicURL,沿用 msext,整体 nonisolated + - `Source/Network/QiniuTokenSigner.swift`:CryptoKit HMAC-SHA1 + Base64URL 自签,等价 msext QiniuManager.m:200-230 + - `Source/Network/QiniuUploader.swift`:actor,`upload(_:timeSec:) async throws -> UploadedFile`,`QNUploadManager(configuration: defaultConfigurationV2)` 路径 + - 真机上传单测待 Phase 3.C 完成(依赖 AudioRecorder 录音输入) - [ ] **3.7** `prepareaudio` handler:拉起录音 → 停止录音 → AMR 转码 → 上传 → 反向 callback `getaudiourl` + `recordSuccess`(仅子游戏触发) ##### 3.D 远程语音回放 @@ -476,9 +478,9 @@ Contract Design Plan(本文档) ##### 4.A SDK 接入 -- [ ] **4.1** `Vendor/WechatSDK/` 拷贝微信 SDK - - Info.plist 加 URL Scheme(`wx586a9b321e56efb7`)+ `LSApplicationQueriesSchemes`(`weixin` / `weixinULAPI` / `weixinURLParamsAPI`) - - Entitlement 加 Associated Domains(Universal Links) +- [x] **4.1** `Vendor/WechatSDK/` 入库微信 SDK(`WechatOpenSDK-NoPay.xcframework` 2.0.5,入 git) + - Info.plist URL Scheme `wx586a9b321e56efb7` + LSApplicationQueriesSchemes `weixin` / `weixinULAPI` / `weixinURLParamsAPI` 三项已就位 + - Universal Links / Associated Domains 暂不接入:企业签分发,不上 App Store,无审核合规需求;`WXApi.registerApp(_, universalLink: "")` 空串即可 - [x] **4.2** **SharePanel 三选一面板 + SharePlatform 框架**(4.B 完成) - `Source/Share/SharePlatform.swift`:SharePlatform 协议(name / isInstalled / share async)+ ShareContent struct(nonisolated Sendable)+ ShareResult enum(success / cancelled / notInstalled / failed)+ ShareScene enum(friend / timeline) - `Source/Share/SharePanel.swift`:底部弹出三按钮(微信 / QQ / 抖音)+ 半透明背景点击关闭 + 0.25s 滑入/滑出动画;与 msext SharePanel.m 等价行为 @@ -504,29 +506,27 @@ Contract Design Plan(本文档) 5. 调起即视为 success - Info.plist 加 `NSPhotoLibraryAddUsageDescription` 文案 - BuildProject 通过 -- [ ] **4.3** `Source/SDK/WeChat/WeChatSDK.swift` 启动注册(Design §4.2 `registerFull` + 全部 12 个 `MMAPP_SUPPORT_*` flag) -- [ ] **4.4** `Source/SDK/WeChat/WeChatManager.swift`(@MainActor) - - 持久 delegate - - `authorize() async throws -> WXAuthCode`(state UUID 配对) - - `share(_:scene:) async throws`(FIFO 串行) - - 详见 Design §8.5 -- [ ] **4.5** `SceneDelegate.openURLContexts` 接入 QQ → WXApi 顺序 +- [x] **4.3** `Source/SDK/WeChat/WeChatSDK.swift` 启动注册(`WXApi.registerApp("wx586a9b321e56efb7", universalLink: "")`;新 SDK 2.x 无 `MMAPP_SUPPORT_*` flag 概念,简单注册即可) +- [x] **4.4** `Source/SDK/WeChat/WeChatManager.swift`(@MainActor) + - 持久 delegate(WXApiDelegate,onResp nonisolated 跳 MainActor) + - `authorize() async throws -> WXAuthCodePayload`(state UUID 配对) + - `shareLink(_:scene:) async throws`(FIFO 串行;type=2/3 截图/远端图待 4.11 / 4.12 Phase 4.F) +- [x] **4.5** `SceneDelegate.openURLContexts` 仅 `WXApi.handleOpen`(QQ 走 URL Scheme 不依赖 SDK,无 SDK 顺序问题) ##### 4.B 授权登录 -- [ ] **4.6** `Source/Login/WeChatAuth.swift`(actor) - - `authorize() async throws -> WeChatUser`:内部调 `WeChatManager.authorize()` 拿 code → 转给后台 `/wechat/login`(或客户端 fallback)→ 拿到 7 字段 user - - 后台未就绪时的 fallback:客户端直拼 `api.weixin.qq.com/sns/oauth2/access_token` —— 文件头标 `// FIXME: 待后台 /wechat/login 就绪后切换为后台中转` -- [ ] **4.7** `accreditlogin` handler:触发 OAuth → 反向 callback `sharelogin`(7 字段,注意 `Province` 大写 P) - - **关键契约测试**:`sharelogin` payload 字段名严格 1:1 +- [x] **4.6** `Source/Login/WeChatAuth.swift`(客户端直拼 sns/oauth2/access_token + sns/userinfo) + - 接受 msext 同款客户端持 secret 路径(父项目 CLAUDE.md 已识别但接受;后台无 `/wechat/login` 中转) + - 7 字段 user 包含 `openid` / `headimgurl` / `nickname` / `sex` / `city` / `Province`(大写 P)/ `unionid` +- [x] **4.7** `accreditlogin` handler:触发 OAuth → 反向 callback `sharelogin`(7 字段,Province 大写 P) ##### 4.C 分享 -- [ ] **4.8** `Source/Share/SharePlatform.swift` 协议 + `WeChatShare` / `NoopSharePlatform(name:"xianliao")` -- [ ] **4.9** `Source/Share/ShareCenter.swift`:策略分发(type=1/2/3 × sharetype=1/2/3,共 9 种组合) -- [ ] **4.10** `friendsSharetypeUrlToptitleDescript` handler:参数解析 → ShareCenter.dispatch → 反向 callback `sharesuccess({success:"2", type:<原 sharefriend>})` -- [ ] **4.11** 截图分享:`getImageWithFullScreenshot` Swift 等价实现(`UIGraphicsImageRenderer`) -- [ ] **4.12** 远程图片分享:URLSession 下载 → 重打包 +- [x] **4.8** `Source/Share/SharePlatform.swift` 协议 + `WechatShare` / `QQShare` / `DouyinShare`(闲聊不接,参 ADR-006) +- [x] **4.9** `Source/Share/ShareCenter.swift`:sharefriend=1 弹 SharePanel;=2 直接微信朋友圈(msext gameController.m:585 等价) +- [x] **4.10** `FriendsShareHandler.swift` → ShareCenter.dispatch → 反向 callback `sharesuccess({success:"2", type:<原 sharefriend>})` +- [ ] **4.11** 截图分享(type=2):`captureScreenshot` → WXImageObject + thumbImage 0.4x(msext gameController.m:637-658 等价)— Phase 4.F +- [ ] **4.12** 远程图片分享(type=其它):`downloadRemote` → WXImageObject + thumbImage 0.4x — Phase 4.F #### 验收 @@ -554,16 +554,17 @@ Contract Design Plan(本文档) #### 任务清单 -- [ ] **5.1** 从 [高德官方下载页](https://lbs.amap.com/api/ios-location-sdk/download) 拉取最新 `AMapLocationKit.xcframework` + `AMapFoundationKit.xcframework`,放 `Vendor/AMap/`;Target → General → Embed Frameworks 加入;按 Design §14.1 Vendor 接入标准流程操作;`Vendor/AMap/README.md` 记录版本号 / 下载日期 -- [ ] **5.2** Info.plist 加 `NSLocationWhenInUseUsageDescription = "{gamehallname}需要访问您的位置以提供本地化服务"` -- [ ] **5.3** `Source/SDK/AMap/AMapWrapper.swift`:启动期 `updatePrivacyShow` + `updatePrivacyAgree` + `apiKey` 设置 -- [ ] **5.4** `Source/Location/LocationService.swift`(actor) - - `requestOnce() async throws -> LocationPayload` - - `startContinuous(onUpdate:)` / `stop()` -- [ ] **5.5** `startlocation` handler:`data=1` 持续 / 其他一次性 → 反向 callback `getlocationinfo` 9 字段 - - **关键契约**:`latitude` / `longitude` 是 **string**(`stringWithFormat:%f` 等价产物) - - **关键契约**:`province` 是小写 p(与 `sharelogin.Province` 大写不同) -- [ ] **5.6** 失败路径:`getlocationinfo({errorCode:12, errorMsg:"缺少定位权限"})` +- [x] **5.1** 高德官方至今**不提供 xcframework**,回退为 fat `.framework` 入 `Vendor/AMap/`(AMapFoundationKit 1.9.0 + AMapLocationKit 2.12.0);Target Do Not Embed(静态库);M Mac 模拟器链接失败是接受的现状,开发期跑真机,详见 `Vendor/AMap/README.md` 「已知限制」 +- [x] **5.2** Info.plist `NSLocationWhenInUseUsageDescription = "进贤聚友棋牌需要访问您的位置以提供本地化服务"` +- [x] **5.3** `Source/SDK/AMap/AMapWrapper.swift`:`AMapLocationManager.updatePrivacyShow` + `updatePrivacyAgree` + `AMapServices.shared().apiKey`(注意:隐私 API 在 AMapLocationManager 类方法上,不在 AMapServices) +- [x] **5.4** `Source/Location/LocationService.swift`(actor) + - `requestOnce() async throws -> LocationPayload` ✅ + - `stop()` 清理钩子 ✅ + - 持续模式 `startContinuous(onUpdate:)` 未实现(msext 实际只有 H5 一次性 startlocation;如未来 H5 用 data=1 持续,再补) +- [x] **5.5** `startlocation` handler:单次定位 → 反向 callback `getlocationinfo` 9 字段 + - latitude/longitude 是 string(`String(format: "%f", ...)`) + - province 是小写 p(与 sharelogin.Province 大写不同) +- [x] **5.6** 失败路径:`getlocationinfo({errorCode:12, errorMsg:"缺少定位权限"})` #### 验收 @@ -911,36 +912,36 @@ H5 调 `OpenurlTitleData` 打开弹层 WebView,弹层内 H5 用 `window.settin - [ ] 2.13 ExternalSubscriptions ### Phase 3 音频体系 -- [ ] 3.1 AudioPlayer +- [x] 3.1 AudioPlayer(Source/Audio/AudioPlayer.swift,srcIsloop / stopBackground / volume 全路径) - [ ] 3.2 本地音频真机验证 -- [ ] 3.3 opencore-amr Vendor 接入 +- [ ] 3.3 opencore-amr Vendor 接入(详见 docs/SDK-Integration-Guide.md §C,需手动) - [ ] 3.4 VoiceCoder Swift wrapper -- [ ] 3.5 AudioRecorder + 麦克风权限 -- [ ] 3.6 七牛 SPM + QiniuUploader +- [ ] 3.5 AudioRecorder + 麦克风权限(NSMicrophoneUsageDescription 已就位) +- [x] 3.6 七牛 SPM(objc-sdk 8.9.2)+ QiniuConfig + QiniuTokenSigner(CryptoKit HMAC-SHA1 自签)+ QiniuUploader actor(initWithConfiguration + defaultConfigurationV2) - [ ] 3.7 prepareaudio handler - [ ] 3.8 mediaTypeAudio handler ### Phase 4 分享 + 登录 -- [ ] 4.1 微信 SDK Vendor + URL Scheme -- ✗ ~~4.2 QQ SDK Vendor~~ **不需要**:QQ 走 URL Scheme,详见 Phase 4.2 任务清单 -- [ ] 4.3 WeChatSDK registerFull -- [ ] 4.4 WeChatManager(state map + FIFO) -- [ ] 4.5 SceneDelegate openURL 顺序 -- [ ] 4.6 WeChatAuth(后台中转或 fallback) -- [ ] 4.7 accreditlogin → sharelogin -- [ ] 4.8 SharePlatform 协议 -- [ ] 4.9 ShareCenter 9 种组合 -- [ ] 4.10 friendsShare... handler -- [ ] 4.11 截图分享 -- [ ] 4.12 远程图片分享 +- [x] 4.1 微信 SDK Vendor + URL Scheme(WechatOpenSDK-NoPay.xcframework 2.0.5 入库 Vendor/WechatSDK/;Info.plist CFBundleURLTypes + LSApplicationQueriesSchemes 3 项) +- ✗ ~~4.2 QQ SDK Vendor~~ **不需要**:QQ 走 URL Scheme,详见 §5 Phase 4.A 任务清单 +- [x] 4.3 WeChatSDK register(Source/SDK/WeChat/WeChatSDK.swift;新 SDK 2.x 无 MMAPP flag 概念,registerApp + universalLink 即可) +- [x] 4.4 WeChatManager(state map + FIFO;Source/SDK/WeChat/WeChatManager.swift) +- [x] 4.5 SceneDelegate openURL(QQ 走 URL Scheme 不依赖 SDK,仅微信 WXApi.handleOpen) +- [x] 4.6 WeChatAuth(客户端直拼 sns/oauth2/access_token + sns/userinfo,沿用 msext 安全模型) +- [x] 4.7 accreditlogin → sharelogin(7 字段,Province 大写 P) +- [x] 4.8 SharePlatform 协议(Phase 4.B 完成)+ WechatShare(type=1 链接 done;type=2/3 见 4.11/4.12) +- [x] 4.9 ShareCenter(sharefriend=1 弹 SharePanel;=2 直接微信朋友圈;Phase 4.B 完成) +- [x] 4.10 friendsShare... handler(FriendsShareHandler.swift,sharesuccess 反向 callback) +- [ ] 4.11 截图分享(Phase 4.F,type=2) +- [ ] 4.12 远程图片分享(Phase 4.F,type=3) ### Phase 5 定位 -- [ ] 5.1 AMap Vendor 接入(XCFramework) -- [ ] 5.2 定位权限文案 -- [ ] 5.3 AMapWrapper 启动注册 -- [ ] 5.4 LocationService -- [ ] 5.5 startlocation handler(latitude/longitude string,province 小写) -- [ ] 5.6 失败回包 errorCode 12 +- [x] 5.1 AMap Vendor 接入(高德官方不提供 XCFramework,回退为 fat `.framework`;M Mac 模拟器链接失败是接受的现状,详见 Vendor/AMap/README.md) +- [x] 5.2 定位权限文案(NSLocationWhenInUseUsageDescription) +- [x] 5.3 AMapWrapper 启动注册(updatePrivacyShow / updatePrivacyAgree 是 AMapLocationManager 类方法,apiKey 在 AMapServices.shared) +- [x] 5.4 LocationService actor(requestOnce 异步 + stop 清理钩子) +- [x] 5.5 startlocation handler(latitude/longitude string,province 小写 p) +- [x] 5.6 失败回包 errorCode 12(缺少定位权限) ### Phase 6 子游戏 - [x] 6.1 AppCoordinator 栈深节流(2s + viewControllers.count < 2)