fallback 链头嵌入根(RemoteConfig 顶层):所有 7 字段真正统一,删字段级特殊兜底
【动机】 上一版"统一接口"虽然合并了 resolve / resolveAudio,但 resolve 内部仍有不对称: - showmessage / audioDomain / audioBucket: pickString(chain) ?? config.xxx - appVersion / appDownload / gameVersion / gameZip: 只 pickInt/String(chain), 顶层声明这 4 个字段也读不到 【方案】 - RemoteConfig 加 4 个 String? 字段(appVersion/appDownload/gameVersion/gameZip) 配齐与节点 struct 同款 7 字段集合 - extension RemoteConfig: RemoteConfigNode(自然 conform) - buildChain 首行 var chain = [config]:根始终作为最浅一层 - resolve() 去掉 ?? config.xxx 三处特殊兜底,全 7 字段一律 pickString/Int(chain) 【效果】 - 节点链 1..5 长(含根);所有字段一视同仁,pickString 倒序找即天然包含顶层 - 任何字段都可以在顶层 / agent / game / channel / market 任一层声明,最深层赢 - 算法对称、代码无字段级特殊分支 【硬约束(Plan ADR-009 同步)】 新增字段:加进协议 + 5 个 struct(含 RemoteConfig)+ ResolvedVersion + resolve() 内一行 pickString/Int(chain, \.xxx);不允许 ?? config.xxx 类回退。 BuildProject 通过。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
485f0c6679
commit
692c849e0d
@@ -10,19 +10,25 @@ import Foundation
|
||||
|
||||
// MARK: - Codable 模型(单链 4 层嵌套:agent → game → channel → market)
|
||||
|
||||
/// 远端配置顶层。所有字段(appVersion / appDownload / gameVersion / gameZip /
|
||||
/// showmessage / audioDomain / audioBucket)都可在顶层声明 ——作为节点链最浅一层
|
||||
/// 参与 4 层 fallback 兜底,与 Agent/Game/Channel/Market 同款语义。
|
||||
nonisolated public struct RemoteConfig: Codable, Sendable {
|
||||
public let appVersion: String?
|
||||
public let appDownload: String?
|
||||
public let gameVersion: String?
|
||||
public let gameZip: String?
|
||||
public let showmessage: String?
|
||||
/// 七牛 CDN 域名(**不带 http:// 前缀**),录音上传后拼公开访问 URL 用。
|
||||
/// 顶层值作为整链最浅层 fallback;与版本字段同款 4 层 fallback:
|
||||
/// agent → game → channel → market 任一层均可声明,最深层赢;都不写则用顶层。
|
||||
/// 启动期由 VersionResolver.resolveAudio(...) 解析,缺失抛 BootError.audioConfigMissing。
|
||||
public let audioDomain: String?
|
||||
/// 七牛 bucket 名(putPolicy.scope)。语义同 audioDomain,同款 4 层 fallback。
|
||||
public let audioBucket: String?
|
||||
public let agentlist: [Agent]?
|
||||
|
||||
public init(from decoder: any Decoder) throws {
|
||||
let c = try decoder.container(keyedBy: CodingKeys.self)
|
||||
appVersion = try c.decodeFlexibleStringIfPresent(forKey: .appVersion)
|
||||
appDownload = try c.decodeFlexibleStringIfPresent(forKey: .appDownload)
|
||||
gameVersion = try c.decodeFlexibleStringIfPresent(forKey: .gameVersion)
|
||||
gameZip = try c.decodeFlexibleStringIfPresent(forKey: .gameZip)
|
||||
showmessage = try c.decodeFlexibleStringIfPresent(forKey: .showmessage)
|
||||
audioDomain = try c.decodeFlexibleStringIfPresent(forKey: .audioDomain)
|
||||
audioBucket = try c.decodeFlexibleStringIfPresent(forKey: .audioBucket)
|
||||
@@ -31,8 +37,10 @@ nonisolated public struct RemoteConfig: Codable, Sendable {
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
// 驼峰 case 名(不带 rawValue),依赖 JSONDecoder.convertFromSnakeCase 自动把
|
||||
// JSON 的 audio_domain / audio_bucket 归一化到 audioDomain / audioBucket
|
||||
case showmessage, agentlist, audioDomain, audioBucket
|
||||
// JSON 的 app_version / audio_domain 等归一化为 appVersion / audioDomain
|
||||
case showmessage, agentlist
|
||||
case appVersion, appDownload, gameVersion, gameZip
|
||||
case audioDomain, audioBucket
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user