【数据流改造(三分制)】 - 微信 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>
71 lines
3.0 KiB
Swift
71 lines
3.0 KiB
Swift
//
|
||
// 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}`
|
||
//
|
||
// ⚠️ 用 CryptoKit(iOS 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 JSON(daoqi 用默认 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: "_")
|
||
}
|
||
}
|