Files
youle_app_ios_v2/ylgamehall/Source/Network/QiniuTokenSigner.swift
T
joywayerandClaude Opus 4.7 83d9502dee ADR-009 凭证集中化:AppSecrets.plist + 七牛运行参数走 RemoteConfig.audio_*(契约影响)
【数据流改造(三分制)】
- 微信 AppID  唯一源 = Info.plist CFBundleURLTypes (URLName=weixin first scheme)
- 应用级凭证 唯一源 = AppSecrets.plist (wxAppSecret/qiniuAccessKey/qiniuSecretKey)
- 七牛运行参数 唯一源 = RemoteConfig 顶层 audio_domain / audio_bucket(远端动态注入)

【契约影响】
- ChannelConfig.plist:11 key → 10 key,移除 qiniudomain(ADR-007 守护规则同步)
- BundleConfig:删除 qiniuDomain 属性
- RemoteConfig:顶层新增可选字段 audioDomain / audioBucket(JSON snake_case 自动归一化)
- 启动期:WebContainerViewController parsed 分支校验 audio_domain/audio_bucket 非空,
  缺失抛 BootError.audioConfigMissing 弹 modal 永停(与 showmessage 同等致命)
- 上线前置:测试 / 生产远端 .txt 配置必须先补 audio_domain / audio_bucket 两个顶层 key
- WeChatSDK.appID / WeChatAuth.appSecret / QiniuConfig.* 调用方零签名变化

【新增】
- ylgamehall/Resources/AppSecrets.plist(3 key)
- ylgamehall/Source/Resource/AppSecrets.swift(单例加载,对齐 BundleConfig 模式)
- QiniuConfig 改 actor:cdnDomain/bucketName 进 actor 状态 + update(...) async;
  accessKey/secretKey 仍 nonisolated(直接读 AppSecrets)
- QiniuTokenSigner.uploadToken() 改 async(bucketName 来自 actor)
- QiniuUploader 预取 cdnDomain 闭包外,SDK 同步 callback 内直接拼 URL

【删除】
- WeChatSDK.swift  static let appID  硬编码 → Info.plist 启动期解析
- WeChatAuth.swift static let appSecret 硬编码 → AppSecrets.shared.wxAppSecret
- QiniuConfig 中 accessKey / secretKey / bucketName / cdnDomain 四处硬编码
- ChannelConfig.plist 的 qiniudomain 字段(plist 与代码双源僵尸字段)

【文档同步】
- Plan:新增 ADR-009 + ADR-007 守护规则改 10 key + §236 BundleConfig 描述
- Design §3.4 多处 "11 项" → "10 项";§7.0.3 plist 示例 + BundleConfig 代码骨架
  + Scripts/inject_channel.sh 同步
- SDK-Integration-Guide §0 凭证位置改三分制 + §尾"七牛域名读取"加新外壳路径
- Verification-Checklist L69 "(11 项)" → "(10 项)"

参考契约章节:docs/Development-Plan.md ADR-009、docs/H5-Native-Implementation-Design.md §7.0.3
BuildProject 通过,Xcode 即时诊断 0 警告。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-27 21:07:55 +08:00

71 lines
3.0 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.
//
// QiniuTokenSigner.swift
// ylgamehall
//
// 七牛上传 token 客户端自签(HMAC-SHA1 + Base64URL)。
// 与 msext QiniuManager.m:200-230 generateUploadToken 等价算法。
//
// putPolicy 结构:
// { "scope": "iosaudio[:key]", "deadline": now + expiresIn }
// 签名流程:
// putPolicy JSON → Base64URL → HMAC-SHA1(secretKey) → Base64URL
// → `${AccessKey}:${sign}:${encodedPolicy}`
//
// ⚠️ 用 CryptoKitiOS 13+),不依赖 Qiniu SDK,纯 Swift 自签 token。
//
import Foundation
import CryptoKit
/// 七牛上传 token 自签。bucketName 来自 actor `QiniuConfig.shared`,需 await
/// accessKey / secretKey 是 AppSecrets 同步取,不需要 await。
public enum QiniuTokenSigner {
/// 生成上传 token。
/// - Parameters:
/// - key: 已废弃参数;保留签名兼容性,但不再写入 putPolicy。daoqi
/// QiniuManager.m:207 的 putPolicy.scope 仅含 bucket(限定 bucket、
/// 允许任意 key),与新外壳上传 key 是分离的两个维度。带 `:key` 后
/// 缀(限定 key 精确匹配)会让七牛后端校验更严,且与 daoqi 行为不一致
/// - expiresIn: token 有效时长(默认 3600 秒)
public static func uploadToken(key: String? = nil, expiresIn: TimeInterval = 3600) async -> String {
_ = key // 参数保留以避免破坏调用方,但不再使用
// 1. scope 仅含 bucket(对齐 daoqi QiniuManager.m:207
// bucketName 由远端 audio_bucket 动态注入,启动期已校验非空
let scope = await QiniuConfig.shared.bucketName
let deadline = Int(Date().timeIntervalSince1970 + expiresIn)
// 2. putPolicy JSONdaoqi 用默认 options:0,不强制排序)
let putPolicy: [String: Any] = [
"scope": scope,
"deadline": deadline
]
let policyData = (try? JSONSerialization.data(withJSONObject: putPolicy, options: [])) ?? Data()
// 3. Base64URL(putPolicy)
let encodedPolicy = base64URLEncode(policyData)
// 4. HMAC-SHA1(secretKey, encodedPolicy) → Base64URL
let secretKeyData = Data(QiniuConfig.secretKey.utf8)
let key = SymmetricKey(data: secretKeyData)
let signature = HMAC<Insecure.SHA1>.authenticationCode(
for: Data(encodedPolicy.utf8),
using: key
)
let encodedSign = base64URLEncode(Data(signature))
// 5. token = AccessKey:encodedSign:encodedPolicy
return "\(QiniuConfig.accessKey):\(encodedSign):\(encodedPolicy)"
}
/// Base64URL: 标准 Base64 把 + → -、/ → _**保留末尾 `=` padding**。
/// 对齐 daoqi QiniuManager.m:240-245 `urlsafeBase64EncodeData:`,七牛后端
/// decode token 时依赖 padding 还原 base64;删 `=` 会触发 401 bad token。
private nonisolated static func base64URLEncode(_ data: Data) -> String {
data.base64EncodedString()
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
}
}