// // QiniuConfig.swift // ylgamehall // // 七牛云配置访问层。 // // 数据来源(单一真相): // - accessKey / secretKey ← AppSecrets.plist(应用级凭证,跨渠道相同) // - bucketName / cdnDomain ← RemoteConfig 顶层 audio_bucket / audio_domain // (远端动态下发,缺失启动期致命,由 WebContainerViewController 校验后 // 调用 update(domain:bucket:) 注入) // // ⚠️ secretKey 客户端持有 = 父项目 CLAUDE.md「已识别但接受的安全风险」, // 同款 wxAppSecret 路径。msext 已分发多年等价泄露,沿用不引入新攻击面。 // import Foundation /// 七牛云配置访问层。actor 隔离让远端注入的 cdnDomain / bucketName 在并发上传场景下 /// 写读安全。accessKey / secretKey 是 nonisolated immutable,无需 actor 屏障。 public actor QiniuConfig { public static let shared = QiniuConfig() // MARK: - 来自 AppSecrets.plist(应用级,启动期一次性加载,运行期不变) /// 七牛 AccessKey — 来源 `AppSecrets.plist.qiniuAccessKey` public nonisolated static var accessKey: String { AppSecrets.shared.qiniuAccessKey } /// 七牛 SecretKey — 来源 `AppSecrets.plist.qiniuSecretKey`。HMAC-SHA1 签 putPolicy 用 public nonisolated static var secretKey: String { AppSecrets.shared.qiniuSecretKey } /// 录音 key 模板前缀(msext QiniuConfig.m:22 为空字符串) public nonisolated static let recordingDirectory = "" // MARK: - 来自 RemoteConfig(远端,启动期动态注入) private var _cdnDomain: String = "" private var _bucketName: String = "" /// 启动期由 WebContainerViewController 在远端配置拉到且校验非空后调用, /// 之后 QiniuUploader / QiniuTokenSigner 即可 await 读取。 public func update(cdnDomain: String, bucketName: String) { _cdnDomain = cdnDomain _bucketName = bucketName } /// CDN 域名(不带 http:// 协议头)。访问前必须先 `update(...)`,否则返回空串 /// 导致后续上传/拼 URL 失败 —— 但启动流水线已保证 update 一定先于任何上传调用 public var cdnDomain: String { _cdnDomain } /// 七牛 bucket 名(putPolicy.scope)。访问前必须先 `update(...)`。 public var bucketName: String { _bucketName } /// 拼访问 URL:`http://{cdnDomain}/{key}`(msext QiniuManager.m:197 等价) /// 协议头固定 http://(与 msext 一致;七牛 CDN 同时支持 https,按需评估)。 public func publicURL(forKey key: String) -> String { "http://\(_cdnDomain)/\(key)" } }