From 9e50c38323f1af0f573fafca24ec00ea4aba9e79 Mon Sep 17 00:00:00 2001 From: joywayer Date: Mon, 22 Jun 2026 02:24:22 +0800 Subject: [PATCH] =?UTF-8?q?Phase=201.12=EF=BC=9ALocalVersionReader=20?= =?UTF-8?q?=E8=AF=BB=20version.xml=20+=20ATS=20=E5=85=A8=E5=B1=80=E6=94=BE?= =?UTF-8?q?=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 LocalVersionReader 读两个本地版本号 + Info.plist 加 NSAllowsArbitraryLoads 全局放行 HTTP(为 Phase 1.13 zip 下载链路准备)。 - 新增 ylgamehall/Source/Resource/LocalVersionReader.swift - public enum LocalVersionReader,nonisolated static 命名空间 - localAppVersion: Int 从 BundleConfig.shared.appVersion 转 Int (沿用 ChannelConfig.plist 注入),解析失败返回 0 - localGameVersion: Int 用 Foundation XMLParser SAX 风格解析 SandboxPaths.lobbyVersionXML 的 /game/version@value 节点, 缺失 / 损坏返回 0(msext NewRootVC.m:664-682 `[nil intValue]=0` 兜底语义,是首装后首次升级的关键机制) - subGameVersion(dir:start:) 子游戏版本号读取,Phase 6 用 - 内部 VersionXMLHandler 标 nonisolated(Swift 6 严格并发下 XMLParserDelegate 跨边界),SAX 风格只在 didStartElement 抓 version 节点的 value 属性 - Info.plist 加 NSAppTransportSecurity / NSAllowsArbitraryLoads = true - 企业签 / 超签分发场景,后端域名易变(zip CDN / 配置服 / 七牛 / 错误日志上报等),白名单维护成本高于安全收益 - 全局放行的副作用主要影响第三方 SDK 流量;本项目无意外引入未审计 SDK,且第三方 SDK 自身一般也走 HTTPS,不受影响 - 详见 ADR-008 决策上下文与 CLAUDE.md「不上架 App Store,企业签 / 超签 / TF 分发」契约 - RootViewController 烟雾测试串接 LocalVersionReader:打印本地 vs 远端 对比 + IPA / zip 升级判定 - 真机实测(现网 .txt 本地 + 当前 IPA 内 gamehall.zip 解压物): - 本地 appVersion = 43, 远端 = 43 → 不升级 - 本地 gameVersion = 260(IPA 内 2023-12 zip 的 version.xml), 远端 = 261 → 需要 zip 升级。Phase 1.13 将实现下载 + 解压 + 替换链路 --- ylgamehall/Info.plist | 9 +++ ylgamehall/RootViewController.swift | 15 ++++ .../Source/Resource/LocalVersionReader.swift | 75 +++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 ylgamehall/Source/Resource/LocalVersionReader.swift diff --git a/ylgamehall/Info.plist b/ylgamehall/Info.plist index 9246d9d..57ae4e7 100644 --- a/ylgamehall/Info.plist +++ b/ylgamehall/Info.plist @@ -19,6 +19,15 @@ + NSAppTransportSecurity + + + NSAllowsArbitraryLoads + + UIRequiresFullScreen UIStatusBarHidden diff --git a/ylgamehall/RootViewController.swift b/ylgamehall/RootViewController.swift index 9ea5a8d..6f76861 100644 --- a/ylgamehall/RootViewController.swift +++ b/ylgamehall/RootViewController.swift @@ -113,6 +113,21 @@ final class RootViewController: UIViewController { gameZip = \(resolved.gameZip ?? "") showmessage = "\(resolved.showmessage ?? "")" """) + + // Phase 1.12 烟雾测试:读本地版本号 + 与远端对比 + let localApp = LocalVersionReader.localAppVersion + let localGame = LocalVersionReader.localGameVersion + let needIPAUpgrade = resolved.appVersion > localApp + let needZipUpgrade = resolved.gameVersion > localGame + print(""" + [LocalVersionReader] 本地 vs 远端对比: + 本地 appVersion = \(localApp)(来自 ChannelConfig.plist) + 远端 appVersion = \(resolved.appVersion) + → IPA 升级 = \(needIPAUpgrade ? "需要" : "不需要") + 本地 gameVersion = \(localGame)(来自 version.xml /game/version@value) + 远端 gameVersion = \(resolved.gameVersion) + → H5 zip 升级 = \(needZipUpgrade ? "需要" : "不需要") + """) case .shortText(let msg): print(""" [RemoteConfigClient] 拉到短文本响应(耗时 \(String(format: "%.3f", elapsed))s) diff --git a/ylgamehall/Source/Resource/LocalVersionReader.swift b/ylgamehall/Source/Resource/LocalVersionReader.swift new file mode 100644 index 0000000..2be5f05 --- /dev/null +++ b/ylgamehall/Source/Resource/LocalVersionReader.swift @@ -0,0 +1,75 @@ +// +// LocalVersionReader.swift +// ylgamehall +// +// 读本地两个版本号: +// - 原生 IPA 版本(来自 ChannelConfig.plist 的 appversion 字段) +// - H5 包版本(来自 Library/Caches/{gamedir}/{gamestart}/version.xml 的 /game/version@value) +// +// 详见 docs/Development-Plan.md ADR-008-F / msext NewRootVC.m:664-682。 +// + +import Foundation + +public enum LocalVersionReader { + + /// 原生 IPA 版本。等价 msext `[FuncPublic filename:@"appversion"] integerValue` + /// 来源:ChannelConfig.plist 的 `appversion` 字段(沿用渠道注入约定, + /// IPA 后处理改 plist 可动态调整)。 + /// 解析失败返回 0(msext `[nil intValue] = 0` 兜底语义)。 + nonisolated public static var localAppVersion: Int { + Int(BundleConfig.shared.appVersion) ?? 0 + } + + /// 大厅 H5 包版本。等价 msext `NewRootVC.m:664-682` 的 GDataXML 路径, + /// 新外壳用 Foundation `XMLParser`(无外部依赖)解析。 + /// + /// 文件路径:`SandboxPaths.lobbyVersionXML` = + /// `Library/Caches/{gamedir}/{gamestart}/version.xml` + /// 解析 `/game/version` 节点的 `value` 属性,转 Int。 + /// + /// **缺失 / 损坏返回 0**(必须保留,首装后首次升级的兜底机制: + /// `[nil intValue] = 0`,任何远端 gameVersion > 0 触发下载)。 + nonisolated public static var localGameVersion: Int { + readGameVersion(from: SandboxPaths.lobbyVersionXML) ?? 0 + } + + /// 子游戏 H5 包版本。等价 msext `gameController.m:1249-1254`。 + nonisolated public static func subGameVersion(dir: String, start: String) -> Int { + let url = SandboxPaths.caches + .appendingPathComponent(dir) + .appendingPathComponent(start) + .appendingPathComponent("version.xml") + return readGameVersion(from: url) ?? 0 + } + + // MARK: - 内部 XML 解析 + + nonisolated private static func readGameVersion(from url: URL) -> Int? { + guard let data = try? Data(contentsOf: url) else { return nil } + let parser = XMLParser(data: data) + let handler = VersionXMLHandler() + parser.delegate = handler + guard parser.parse(), let v = handler.gameVersion else { return nil } + return v + } +} + +// MARK: - XMLParser delegate + +/// 仅提取 ``(在 `/game/version` 路径下)的 value 转 Int。 +/// msext 的 xpath 是 `/game/version` 取属性 `value`;新外壳用 SAX 风格 XMLParser +/// 也只需要在 `version` 元素出现时取属性即可(嵌套层级宽容化处理)。 +nonisolated private final class VersionXMLHandler: NSObject, XMLParserDelegate { + var gameVersion: Int? + + func parser(_ parser: XMLParser, + didStartElement elementName: String, + namespaceURI: String?, + qualifiedName qName: String?, + attributes attributeDict: [String: String] = [:]) { + if elementName == "version", let raw = attributeDict["value"], let v = Int(raw) { + gameVersion = v + } + } +}