对齐后端消息类型约定,去掉 route/uri 等兼容字段,并补充联调日志与单测。 Co-authored-by: Cursor <cursoragent@cursor.com>
87 lines
2.8 KiB
Swift
87 lines
2.8 KiB
Swift
import Foundation
|
||
|
||
/// 推送点击后可到达的业务页面。
|
||
enum PushDestination: Sendable, Equatable {
|
||
case paymentRecord
|
||
case paymentDetails
|
||
case messageCenter
|
||
}
|
||
|
||
/// 极光/APNs payload 解析结果,仅提取业务消息类型用于点击路由。
|
||
struct PushPayload: Sendable, Equatable {
|
||
private let type: String
|
||
|
||
/// 从系统通知 userInfo 提取顶层或推送包装层中的业务消息类型。
|
||
nonisolated init(userInfo: [AnyHashable: Any]) {
|
||
type = Self.extractType(from: userInfo)
|
||
}
|
||
|
||
/// 从可发送的字符串字典创建 payload,仅保留业务消息类型。
|
||
nonisolated init(values: [String: String]) {
|
||
type = Self.normalizedType(values["type"])
|
||
}
|
||
|
||
/// 可发送的规范化字段快照。
|
||
nonisolated var normalizedValues: [String: String] {
|
||
type.isEmpty ? [:] : ["type": type]
|
||
}
|
||
|
||
/// 仅根据后端业务消息类型解析目标页面。
|
||
nonisolated var destination: PushDestination {
|
||
switch type {
|
||
case "1":
|
||
return .paymentRecord
|
||
case "6":
|
||
return .paymentDetails
|
||
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 let nestedTypeContainerKeys = [
|
||
"extras", "extra", "JMessageExtra", "n_extras",
|
||
]
|
||
}
|