Files
youle_app_ios_v2/ylgamehall/RootViewController.swift
T
joywayer 9e50c38323 Phase 1.12:LocalVersionReader 读 version.xml + ATS 全局放行
新增 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 将实现下载 +
    解压 + 替换链路
2026-06-22 02:24:22 +08:00

154 lines
6.7 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// RootViewController.swift
// ylgamehall
//
// M0 阶段临时根控制器,后续将被 Coordinator + LobbyViewController 替换。
//
import UIKit
final class RootViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .black
// Phase 1.2 烟雾测试:打印 ChannelConfig.plist 的 11 项渠道注入值
let cfg = BundleConfig.shared
print("""
[BundleConfig] 渠道注入读取结果:
qiniuDomain = \(cfg.qiniuDomain)
gameId = \(cfg.gameId)
channel = \(cfg.channel)
gameDir = \(cfg.gameDir)
gameStart = \(cfg.gameStart)
gameConfig = \(cfg.gameConfig)
market = \(cfg.market)
agent = \(cfg.agent)
appVersion = \(cfg.appVersion)
other = "\(cfg.other)"
appleConfig = "\(cfg.appleConfig)"
""")
// Phase 1.3 烟雾测试:打印 SandboxPaths 计算路径
print("""
[SandboxPaths] 路径计算结果:
caches = \(SandboxPaths.caches.path)
documents = \(SandboxPaths.documents.path)
bundle = \(SandboxPaths.bundle.path)
lobbyRoot = \(SandboxPaths.lobbyRoot.path)
lobbyIndex = \(SandboxPaths.lobbyIndex.path)
lobbyVersionXML = \(SandboxPaths.lobbyVersionXML.path)
""")
// Phase 1.5 烟雾测试:触发 ResourceUnzipper,确认 lobby index.html 存在
Task {
let start = Date()
do {
try await ResourceUnzipper.shared.ensureReady()
let elapsed = Date().timeIntervalSince(start)
let exists = FileManager.default.fileExists(atPath: SandboxPaths.lobbyIndex.path)
print("""
[ResourceUnzipper] ensureReady 完成(耗时 \(String(format: "%.3f", elapsed))s
lobbyIndex 存在: \(exists)
""")
} catch {
print("[ResourceUnzipper] ERROR: \(error)")
}
}
// Phase 1.8 烟雾测试:确认 WebViewJavascriptBridge.js 已入 Bundle 且可读
// 注:synchronized group 会把 ylgamehall/Resources/JS/ 子目录扁平化为 bundle 根,
// 所以无需 subdirectory 参数。
if let url = Bundle.main.url(forResource: "WebViewJavascriptBridge",
withExtension: "js"),
let content = try? String(contentsOf: url, encoding: .utf8) {
print("""
[WVJB.js] 已就绪
path = \(url.path)
bytes = \(content.utf8.count)
head = \(content.prefix(60).replacingOccurrences(of: "\n", with: " ⏎ "))
""")
} else {
print("[WVJB.js] ERROR: 找不到 Bundle 内 JS/WebViewJavascriptBridge.js")
}
// Phase 1.10 烟雾测试:拉远端渠道配置 .txt(含 cache-busting + 短文本响应识别)
Task {
let start = Date()
do {
let outcome = try await RemoteConfigClient.shared.fetch()
let elapsed = Date().timeIntervalSince(start)
switch outcome {
case .parsed(let cfg):
print("""
[RemoteConfigClient] 拉取成功 .parsed(耗时 \(String(format: "%.3f", elapsed))s
showmessage = \(cfg.showmessage ?? "<nil>")
agentlist count = \(cfg.agentlist?.count ?? 0)
""")
if let first = cfg.agentlist?.first {
print("""
agent[0].agentid = \(first.agentid ?? "<nil>")
agent[0].appVersion = \(first.appVersion ?? "<nil>")
agent[0].gameVersion = \(first.gameVersion ?? "<nil>")
agent[0].channellist = \(first.channellist?.count ?? 0)
agent[0].gamelist = \(first.gamelist?.count ?? 0)
""")
}
// Phase 1.11 烟雾测试:chulishengji 双子树合并
let bc = BundleConfig.shared
let resolved = VersionResolver.resolve(
config: cfg,
agentId: bc.agent,
channelId: bc.channel,
marketId: bc.market,
gameId: bc.gameId
)
print("""
[VersionResolver] chulishengji 双子树合并结果:
appVersion = \(resolved.appVersion)
appDownload = \(resolved.appDownload ?? "<nil>")
gameVersion = \(resolved.gameVersion)
gameZip = \(resolved.gameZip ?? "<nil>")
showmessage = "\(resolved.showmessage ?? "<nil>")"
""")
// 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
运营杀手锏:应当弹窗显示并停止重试
内容 = "\(msg)"
""")
}
} catch {
print("[RemoteConfigClient] ERROR: \(error)")
}
}
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
.landscape
}
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
.landscapeRight
}
override var prefersStatusBarHidden: Bool { false }
}