108 lines
3.9 KiB
Swift
108 lines
3.9 KiB
Swift
|
|
import Foundation
|
|||
|
|
|
|||
|
|
/// 上传桥接协议常量与 registerTask 参数解析(P3 计划 Task 8)。
|
|||
|
|
///
|
|||
|
|
/// 与 Android 侧 `UploadProtocol.kt` 逐字对齐:通道名、方法名、事件名、
|
|||
|
|
/// 失败码完全一致;Dart 侧 `lib/features/uploads/native_transport.dart`
|
|||
|
|
/// 的 [UploadBridgeChannels] 为同一协议的单源镜像。
|
|||
|
|
enum UploadBridge {
|
|||
|
|
/// 命令通道(registerTask / cancelTask / queryTask / ackEvents)。
|
|||
|
|
static let methodChannel = "photolive/uploads"
|
|||
|
|
|
|||
|
|
/// 事件流(progress / completed / needsRenew / failed,带单调 seq)。
|
|||
|
|
static let eventChannel = "photolive/uploads/events"
|
|||
|
|
|
|||
|
|
static let methodRegister = "registerTask"
|
|||
|
|
static let methodCancel = "cancelTask"
|
|||
|
|
static let methodQuery = "queryTask"
|
|||
|
|
static let methodAck = "ackEvents"
|
|||
|
|
|
|||
|
|
static let eventProgress = "progress"
|
|||
|
|
static let eventCompleted = "completed"
|
|||
|
|
static let eventNeedsRenew = "needsRenew"
|
|||
|
|
static let eventFailed = "failed"
|
|||
|
|
|
|||
|
|
/// 网络不可达/超时(重试上限耗尽后上报)。
|
|||
|
|
static let failNetwork = "NETWORK"
|
|||
|
|
|
|||
|
|
/// 4xx 客户端错误(status 随事件携带;403 单独走 needsRenew)。
|
|||
|
|
static let failHttp4xx = "HTTP_4XX"
|
|||
|
|
|
|||
|
|
/// 5xx 服务端错误(重试上限耗尽后上报)。
|
|||
|
|
static let failServerError = "SERVER_ERROR"
|
|||
|
|
|
|||
|
|
/// 源文件缺失(受管副本被清理,不可恢复)。
|
|||
|
|
static let failSourceMissing = "SOURCE_MISSING"
|
|||
|
|
|
|||
|
|
/// 后台 URLSession 标识(App 重启后系统按此重联会话)。
|
|||
|
|
static let sessionIdentifier = "com.photolive.photo_live.uploads"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// registerTask 参数(Dart → 原生),字段与 Kotlin `UploadRegisterSpec` 一致。
|
|||
|
|
struct UploadRegisterSpec {
|
|||
|
|
let taskId: String
|
|||
|
|
let url: String
|
|||
|
|
let method: String
|
|||
|
|
let headers: [String: String]
|
|||
|
|
let filePath: String
|
|||
|
|
let uploadId: String?
|
|||
|
|
let attempt: Int
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 参数形状不符(插件层转 bad_args 错误,镜像 Kotlin IllegalArgumentException)。
|
|||
|
|
struct UploadBridgeBadArgsError: Error {
|
|||
|
|
let message: String
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// registerTask 参数解析(纯函数;与 Kotlin `parseRegisterSpec` 同语义)。
|
|||
|
|
///
|
|||
|
|
/// 通道解码所得字典为 [String: Any](Flutter 标准编解码 null → NSNull),
|
|||
|
|
/// 显式 null 与类型不符同样归为 bad_args。
|
|||
|
|
func parseRegisterSpec(_ args: [String: Any]) throws -> UploadRegisterSpec {
|
|||
|
|
func str(_ key: String) throws -> String {
|
|||
|
|
guard let v = args[key] as? String else {
|
|||
|
|
throw UploadBridgeBadArgsError(
|
|||
|
|
message: args[key] == nil ? "missing '\(key)'" : "'\(key)' must be a String"
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
return v
|
|||
|
|
}
|
|||
|
|
let taskId = try str("taskId")
|
|||
|
|
let url = try str("url")
|
|||
|
|
let methodRaw = try str("method")
|
|||
|
|
let method = methodRaw.isEmpty ? "PUT" : methodRaw
|
|||
|
|
let filePath = try str("filePath")
|
|||
|
|
var headers: [String: String] = [:]
|
|||
|
|
if let raw = args["headers"] as? [String: Any] {
|
|||
|
|
for (k, v) in raw {
|
|||
|
|
guard let value = v as? String else {
|
|||
|
|
throw UploadBridgeBadArgsError(message: "'headers' must be Map<String, String>")
|
|||
|
|
}
|
|||
|
|
headers[k] = value
|
|||
|
|
}
|
|||
|
|
} else if args["headers"] != nil {
|
|||
|
|
throw UploadBridgeBadArgsError(message: "'headers' must be a Map")
|
|||
|
|
}
|
|||
|
|
var uploadId: String?
|
|||
|
|
var attempt = 0
|
|||
|
|
if let metadata = args["metadata"] as? [String: Any] {
|
|||
|
|
uploadId = metadata["uploadId"] as? String
|
|||
|
|
if let v = metadata["attempt"] as? NSNumber {
|
|||
|
|
attempt = v.intValue
|
|||
|
|
} else if let v = metadata["attempt"] as? Int {
|
|||
|
|
attempt = v
|
|||
|
|
} else if metadata["attempt"] != nil {
|
|||
|
|
throw UploadBridgeBadArgsError(message: "'metadata.attempt' must be an int")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return UploadRegisterSpec(
|
|||
|
|
taskId: taskId,
|
|||
|
|
url: url,
|
|||
|
|
method: method,
|
|||
|
|
headers: headers,
|
|||
|
|
filePath: filePath,
|
|||
|
|
uploadId: uploadId,
|
|||
|
|
attempt: attempt
|
|||
|
|
)
|
|||
|
|
}
|