143 lines
5.3 KiB
Swift
143 lines
5.3 KiB
Swift
import Foundation
|
|
|
|
/// 推送点击后可到达的业务页面。
|
|
enum PushDestination: Sendable, Equatable {
|
|
case orders
|
|
case paymentDetails
|
|
case messageCenter
|
|
case aiRetouchTaskList
|
|
case aiRetouchTaskDetail(batchId: Int)
|
|
}
|
|
|
|
/// 极光/APNs payload 解析结果,仅提取业务消息类型用于点击路由。
|
|
struct PushPayload: Sendable, Equatable {
|
|
private let type: String
|
|
private let aiRetouchBatchId: Int?
|
|
|
|
/// 从系统通知 userInfo 提取顶层或推送包装层中的业务消息类型。
|
|
nonisolated init(userInfo: [AnyHashable: Any]) {
|
|
type = Self.extractType(from: userInfo)
|
|
aiRetouchBatchId = Self.extractBatchId(from: userInfo)
|
|
}
|
|
|
|
/// 从可发送的字符串字典创建 payload,仅保留业务消息类型。
|
|
nonisolated init(values: [String: String]) {
|
|
type = Self.normalizedType(values["type"])
|
|
aiRetouchBatchId = Int(values["ai_retouch_batch_id"] ?? "").flatMap { $0 > 0 ? $0 : nil }
|
|
}
|
|
|
|
/// 可发送的规范化字段快照。
|
|
nonisolated var normalizedValues: [String: String] {
|
|
var values = type.isEmpty ? [:] : ["type": type]
|
|
if let aiRetouchBatchId { values["ai_retouch_batch_id"] = String(aiRetouchBatchId) }
|
|
return values
|
|
}
|
|
|
|
/// 仅根据后端业务消息类型解析目标页面。
|
|
nonisolated var destination: PushDestination {
|
|
switch type {
|
|
case "1", "5":
|
|
return .orders
|
|
case "6":
|
|
return .paymentDetails
|
|
case "14":
|
|
if let aiRetouchBatchId, aiRetouchBatchId > 0 {
|
|
return .aiRetouchTaskDetail(batchId: aiRetouchBatchId)
|
|
}
|
|
return .aiRetouchTaskList
|
|
default:
|
|
return .messageCenter
|
|
}
|
|
}
|
|
|
|
private nonisolated static func extractType(
|
|
from dictionary: [AnyHashable: Any],
|
|
depth: Int = 0
|
|
) -> String {
|
|
guard depth <= 4 else { return "" }
|
|
let directType = normalizedType(dictionary["type"])
|
|
guard directType.isEmpty else { return directType }
|
|
|
|
for key in nestedTypeContainerKeys {
|
|
guard let value = dictionary[key] else { continue }
|
|
if let nested = value as? [AnyHashable: Any] {
|
|
let nestedType = extractType(from: nested, depth: depth + 1)
|
|
if !nestedType.isEmpty { return nestedType }
|
|
continue
|
|
}
|
|
|
|
guard let text = value as? String,
|
|
let data = text.data(using: .utf8),
|
|
let object = try? JSONSerialization.jsonObject(with: data),
|
|
let nested = object as? [String: Any]
|
|
else { continue }
|
|
|
|
let bridged = Dictionary(uniqueKeysWithValues: nested.map {
|
|
(AnyHashable($0.key), $0.value)
|
|
})
|
|
let nestedType = extractType(from: bridged, depth: depth + 1)
|
|
if !nestedType.isEmpty { return nestedType }
|
|
}
|
|
return ""
|
|
}
|
|
|
|
private nonisolated static func normalizedType(_ value: Any?) -> String {
|
|
switch value {
|
|
case let string as String:
|
|
return string.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
case let number as NSNumber:
|
|
return number.stringValue
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
private nonisolated static func extractBatchId(
|
|
from dictionary: [AnyHashable: Any],
|
|
depth: Int = 0
|
|
) -> Int? {
|
|
guard depth <= 5 else { return nil }
|
|
if let value = normalizedPositiveInt(dictionary["ai_retouch_batch_id"]) { return value }
|
|
|
|
if let data = dictionary["data"] {
|
|
if let nested = data as? [AnyHashable: Any],
|
|
let value = extractBatchId(from: nested, depth: depth + 1) { return value }
|
|
if let text = data as? String,
|
|
let nested = jsonDictionary(text),
|
|
let value = extractBatchId(from: nested, depth: depth + 1) { return value }
|
|
}
|
|
|
|
for key in nestedTypeContainerKeys {
|
|
guard let value = dictionary[key] else { continue }
|
|
if let nested = value as? [AnyHashable: Any],
|
|
let batchId = extractBatchId(from: nested, depth: depth + 1) { return batchId }
|
|
if let text = value as? String,
|
|
let nested = jsonDictionary(text),
|
|
let batchId = extractBatchId(from: nested, depth: depth + 1) { return batchId }
|
|
}
|
|
return nil
|
|
}
|
|
|
|
private nonisolated static func normalizedPositiveInt(_ value: Any?) -> Int? {
|
|
let parsed: Int?
|
|
switch value {
|
|
case let number as NSNumber: parsed = number.intValue
|
|
case let string as String: parsed = Int(string.trimmingCharacters(in: .whitespacesAndNewlines))
|
|
default: parsed = nil
|
|
}
|
|
return parsed.flatMap { $0 > 0 ? $0 : nil }
|
|
}
|
|
|
|
private nonisolated static func jsonDictionary(_ text: String) -> [AnyHashable: Any]? {
|
|
guard let data = text.data(using: .utf8),
|
|
let object = try? JSONSerialization.jsonObject(with: data),
|
|
let dictionary = object as? [String: Any]
|
|
else { return nil }
|
|
return Dictionary(uniqueKeysWithValues: dictionary.map { (AnyHashable($0.key), $0.value) })
|
|
}
|
|
|
|
private nonisolated static let nestedTypeContainerKeys = [
|
|
"extras", "extra", "JMessageExtra", "n_extras",
|
|
]
|
|
}
|