Files
youle_app_ios_v2/ylgamehall/RootViewController.swift
T
joywayer 1bc287bafc Phase 1.10:RemoteConfigClient 完整版(HTTPS + cache-busting + 短文本 + FlexibleString)
第二轮调研 daoqi/NewRootVC.m 全文核对后,把对原项目远程配置流程的精确
理解固化进 ADR-008 + Design §6.3.2,并补齐 RemoteConfigClient 缺失的
3 个契约(cache-busting query、短文本响应识别、字段类型混乱兼容)。

核心修正:线上热路径是 chulishengji 双子树合并算法(NewRootVC.m:1372-1538),
不是 onnet 简单 4 层覆盖。viewWillAppear 与 gonet 都会强制把当前 agent 的
gamelist 提到 self.gamelist,只要服务器 JSON 含 agent.gamelist(线上 100% 都有),
self.gamelist 就非 nil,走 else 分支 → chulishengji。本次烟雾测试实测:
agent[0].gamelist = 7 个,完美印证 chulishengji 必要性。

- RemoteConfigClient 新增 cache-busting query:?vXXXXXXXXYYYYYYYY(两段 8 位 hex
  连写,无 =,照搬 msext NewRootVC.m:1226 契约),每次重试都重新生成
- 新增 RemoteConfigOutcome enum:.parsed(RemoteConfig) 与 .shortText(String)
  两种成功状态。短文本响应(trim 后 utf16.count ≤ 100)是服务端运营杀手锏 #1,
  msext 当 alert 文本弹窗 + 停止重试(NewRootVC.m:1239-1243)
- UTF-8 解码 + trim 跟 msext NewRootVC.m:1237-1238 严格对齐
- 所有 String? 字段改用 decodeFlexibleStringIfPresent 扩展(兼容 number-as-string):
  agentid / channelid / marketid / gameid / showmessage / appVersion / appDownload
  / gameVersion / gameZip 全部 9 字段。msext 用 NSNumber.intValue/description 静默
  吞下 number,新外壳 Codable 严格类型必须显式处理
- 远端 URL 从 http 改为 https(后台已切,无需 ATS 例外)
- Plan ADR-008 修订为含 9 个子节(A-I)的精确决策记录,含 chulishengji
  双子树算法、agent 子树结构、game 子树结构、字段级合并优先级、决策顺序、
  与 msext 差异表
- Design §6.3.2 VersionResolver 重写:从"简单 4 层 reduce"改为"chulishengji
  双子树合并 + 字段级合并",附 13 项单测覆盖矩阵
- RootViewController 烟雾测试切换为 outcome enum switch case,覆盖 .parsed
  与 .shortText 两条路径
- 实测烟雾:HTTPS 拉到 30 KB JSON 解析 .parsed 成功(耗时 2.7s 含 TLS 握手),
  agent[0].gamelist 含 7 个 game 节点,证明 chulishengji 路径是热路径
2026-06-22 02:14:57 +08:00

121 lines
4.8 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)
""")
}
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 }
}