fix: 推送点击仅按 type 路由到收款页或消息中心。
对齐后端消息类型约定,去掉 route/uri 等兼容字段,并补充联调日志与单测。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -2,129 +2,85 @@ import Foundation
|
||||
|
||||
/// 推送点击后可到达的业务页面。
|
||||
enum PushDestination: Sendable, Equatable {
|
||||
case payment
|
||||
case orders
|
||||
case verificationOrders
|
||||
case task
|
||||
case queue
|
||||
case paymentRecord
|
||||
case paymentDetails
|
||||
case messageCenter
|
||||
|
||||
/// 目标页面是否依赖当前景区上下文。
|
||||
var requiresScenicContext: Bool {
|
||||
switch self {
|
||||
case .payment, .task, .queue:
|
||||
true
|
||||
case .orders, .verificationOrders, .messageCenter:
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 极光/APNs payload 解析结果,兼容顶层字段及常见 JSON 嵌套格式。
|
||||
/// 极光/APNs payload 解析结果,仅提取业务消息类型用于点击路由。
|
||||
struct PushPayload: Sendable, Equatable {
|
||||
private let values: [String: String]
|
||||
private let type: String
|
||||
|
||||
/// 从系统通知 userInfo 解析路由字段。
|
||||
/// 从系统通知 userInfo 提取顶层或推送包装层中的业务消息类型。
|
||||
nonisolated init(userInfo: [AnyHashable: Any]) {
|
||||
var flattened: [String: String] = [:]
|
||||
Self.mergeDictionary(userInfo, into: &flattened)
|
||||
values = flattened
|
||||
type = Self.extractType(from: userInfo)
|
||||
}
|
||||
|
||||
/// 从可发送的字符串字典创建 payload,供跨 Actor 回调及测试使用。
|
||||
/// 从可发送的字符串字典创建 payload,仅保留业务消息类型。
|
||||
nonisolated init(values: [String: String]) {
|
||||
self.values = values
|
||||
type = Self.normalizedType(values["type"])
|
||||
}
|
||||
|
||||
/// 可发送的规范化字段快照。
|
||||
nonisolated var normalizedValues: [String: String] {
|
||||
values
|
||||
type.isEmpty ? [:] : ["type": type]
|
||||
}
|
||||
|
||||
/// 根据后端约定和历史兼容字段解析目标页面。
|
||||
/// 仅根据后端业务消息类型解析目标页面。
|
||||
nonisolated var destination: PushDestination {
|
||||
let type = value(for: "type")
|
||||
let route = value(for: "route")
|
||||
let uri = value(for: "uri")
|
||||
let action = value(for: "action")
|
||||
let merged = [route, uri, action].joined(separator: " ").lowercased()
|
||||
|
||||
if type == "1" || type == "6"
|
||||
|| merged.contains("payment") || merged.contains("payment_qr")
|
||||
|| merged.contains("收款") {
|
||||
return .payment
|
||||
}
|
||||
if merged.contains("queue") || merged.contains("scenic-queue")
|
||||
|| merged.contains("排队") || merged.contains("叫号") {
|
||||
return .queue
|
||||
}
|
||||
if merged.contains("verification") || merged.contains("writeoff")
|
||||
|| merged.contains("write_off") || merged.contains("核销") {
|
||||
return .verificationOrders
|
||||
}
|
||||
if merged.contains("order") || merged.contains("订单") {
|
||||
return .orders
|
||||
}
|
||||
if merged.contains("task") || merged.contains("任务") {
|
||||
return .task
|
||||
}
|
||||
return .messageCenter
|
||||
}
|
||||
|
||||
private nonisolated func value(for key: String) -> String {
|
||||
values[key]?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
}
|
||||
|
||||
private nonisolated static func mergeDictionary(
|
||||
_ dictionary: [AnyHashable: Any],
|
||||
into result: inout [String: String]
|
||||
) {
|
||||
for (rawKey, value) in dictionary {
|
||||
guard let key = rawKey as? String else { continue }
|
||||
merge(value, key: key, into: &result)
|
||||
switch type {
|
||||
case "1":
|
||||
return .paymentRecord
|
||||
case "6":
|
||||
return .paymentDetails
|
||||
default:
|
||||
return .messageCenter
|
||||
}
|
||||
}
|
||||
|
||||
private nonisolated static func merge(
|
||||
_ value: Any,
|
||||
key: String,
|
||||
into result: inout [String: String]
|
||||
) {
|
||||
if let dictionary = value as? [String: Any] {
|
||||
result[key] = stringValue(value)
|
||||
mergeDictionary(dictionary, into: &result)
|
||||
return
|
||||
}
|
||||
if let dictionary = value as? [AnyHashable: Any] {
|
||||
result[key] = stringValue(value)
|
||||
mergeDictionary(dictionary, into: &result)
|
||||
return
|
||||
}
|
||||
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 }
|
||||
|
||||
let text = stringValue(value)
|
||||
result[key] = text
|
||||
guard Self.nestedJSONKeys.contains(key),
|
||||
let data = text.data(using: .utf8),
|
||||
let object = try? JSONSerialization.jsonObject(with: data)
|
||||
else { return }
|
||||
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
|
||||
}
|
||||
|
||||
if let dictionary = object as? [String: Any] {
|
||||
mergeDictionary(dictionary, into: &result)
|
||||
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 stringValue(_ value: Any) -> String {
|
||||
private nonisolated static func normalizedType(_ value: Any?) -> String {
|
||||
switch value {
|
||||
case let string as String:
|
||||
string
|
||||
return string.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
case let number as NSNumber:
|
||||
number.stringValue
|
||||
return number.stringValue
|
||||
default:
|
||||
String(describing: value)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
private nonisolated static let nestedJSONKeys: Set<String> = [
|
||||
"extras", "extra", "data", "JMessageExtra", "n_extras",
|
||||
private nonisolated static let nestedTypeContainerKeys = [
|
||||
"extras", "extra", "JMessageExtra", "n_extras",
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user