Add APNs push notification registration and payload routing.
Introduce AppDelegate, explicit Info.plist and entitlements, and wire PushNotificationManager into login lifecycle for device token upload and notification handling. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
99
suixinkan/Core/Push/PushPayload.swift
Normal file
99
suixinkan/Core/Push/PushPayload.swift
Normal file
@ -0,0 +1,99 @@
|
||||
//
|
||||
// PushPayload.swift
|
||||
// suixinkan
|
||||
//
|
||||
// Created by Codex on 2026/6/26.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// APNs token 工具,把系统返回的 Data 转成后端可接收的小写十六进制字符串。
|
||||
enum APNsDeviceToken {
|
||||
static func hexString(from data: Data) -> String {
|
||||
data.map { String(format: "%02x", $0) }.joined()
|
||||
}
|
||||
}
|
||||
|
||||
/// 推送 payload 解析器,兼容顶层字段和第三方通道常见 extras/data 嵌套结构。
|
||||
struct PushPayload: Sendable {
|
||||
enum Route: Sendable, Equatable {
|
||||
case payment
|
||||
case order
|
||||
case verificationOrder
|
||||
case task
|
||||
case queue
|
||||
case messageCenter
|
||||
}
|
||||
|
||||
private let values: [String: String]
|
||||
|
||||
nonisolated init(userInfo: [AnyHashable: Any]) {
|
||||
var result: [String: String] = [:]
|
||||
for (key, value) in userInfo {
|
||||
guard let key = key as? String else { continue }
|
||||
result[key] = Self.stringValue(value)
|
||||
if key == "extras" || key == "extra" || key == "data" || key == "JMessageExtra" {
|
||||
Self.mergeJSON(value, into: &result)
|
||||
}
|
||||
}
|
||||
values = result
|
||||
}
|
||||
|
||||
nonisolated var route: Route {
|
||||
let typeText = values["type"] ?? ""
|
||||
let routeText = values["route"] ?? ""
|
||||
let uriText = values["uri"] ?? ""
|
||||
let actionText = values["action"] ?? ""
|
||||
let merged = [typeText, routeText, uriText, actionText].joined(separator: " ").lowercased()
|
||||
|
||||
if typeText == "1" || merged.contains("payment") || merged.contains("pay") || merged.contains("收款") {
|
||||
return .payment
|
||||
}
|
||||
if merged.contains("queue") || merged.contains("排队") || merged.contains("叫号") || uriText == "/scenic-queue" {
|
||||
return .queue
|
||||
}
|
||||
if merged.contains("verification") || merged.contains("writeoff") || merged.contains("核销") {
|
||||
return .verificationOrder
|
||||
}
|
||||
if merged.contains("order") || merged.contains("订单") {
|
||||
return .order
|
||||
}
|
||||
if merged.contains("task") || merged.contains("任务") {
|
||||
return .task
|
||||
}
|
||||
return .messageCenter
|
||||
}
|
||||
|
||||
nonisolated private static func mergeJSON(_ value: Any, into result: inout [String: String]) {
|
||||
if let dict = value as? [String: Any] {
|
||||
for (key, value) in dict {
|
||||
result[key] = stringValue(value)
|
||||
mergeJSON(value, into: &result)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
guard let text = value as? String,
|
||||
let data = text.data(using: .utf8),
|
||||
let object = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
|
||||
else { return }
|
||||
|
||||
for (key, value) in object {
|
||||
result[key] = stringValue(value)
|
||||
mergeJSON(value, into: &result)
|
||||
}
|
||||
}
|
||||
|
||||
nonisolated private static func stringValue(_ value: Any?) -> String {
|
||||
switch value {
|
||||
case let string as String:
|
||||
return string
|
||||
case let number as NSNumber:
|
||||
return number.stringValue
|
||||
case .some(let value):
|
||||
return "\(value)"
|
||||
case nil:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user