持久化用户id
This commit is contained in:
parent
b95420ef74
commit
6323b1d2c9
97
dimensionhub/Core/KeychainHelper.swift
Normal file
97
dimensionhub/Core/KeychainHelper.swift
Normal file
@ -0,0 +1,97 @@
|
||||
//
|
||||
// KeychainHelper.swift
|
||||
// dimensionhub
|
||||
//
|
||||
// Created by 安礼成 on 2025/6/26.
|
||||
//
|
||||
import Foundation
|
||||
import Security
|
||||
import UIKit
|
||||
|
||||
struct KeychainHelper {
|
||||
|
||||
// MARK: - 设备标识符获取
|
||||
static func getPersistentUserId() -> String {
|
||||
let keychainAccount = "com.jihe.dimensionhub.deviceUUID"
|
||||
|
||||
// 1. 尝试从Keychain读取
|
||||
if let uuid = load(key: keychainAccount) {
|
||||
return uuid
|
||||
}
|
||||
|
||||
// 2. 生成新的UUID
|
||||
let newUUID = generatorUserId()
|
||||
|
||||
// 3. 尝试保存到Keychain
|
||||
let data = newUUID.data(using: .utf8)!
|
||||
if !save(key: keychainAccount, data: data) {
|
||||
NSLog("save uuid to keychain error")
|
||||
}
|
||||
|
||||
return newUUID
|
||||
}
|
||||
|
||||
/// 从 Keychain 加载字符串
|
||||
private static func load(key: String) -> String? {
|
||||
if let data = loadFromSecurity(key: key) {
|
||||
return String(data: data, encoding: .utf8)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/// 删除 Keychain 中的项
|
||||
static func delete(key: String) -> Bool {
|
||||
let query = [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrAccount as String: key
|
||||
] as [String: Any]
|
||||
|
||||
let status = SecItemDelete(query as CFDictionary)
|
||||
return status == errSecSuccess || status == errSecItemNotFound
|
||||
}
|
||||
|
||||
// MARK: - 私有方法
|
||||
|
||||
private static func save(key: String, data: Data) -> Bool {
|
||||
let query = [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrAccount as String: key,
|
||||
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
|
||||
kSecValueData as String: data
|
||||
] as [String: Any]
|
||||
|
||||
// 先删除已存在的项
|
||||
SecItemDelete(query as CFDictionary)
|
||||
|
||||
// 添加新项
|
||||
let status = SecItemAdd(query as CFDictionary, nil)
|
||||
return status == errSecSuccess
|
||||
}
|
||||
|
||||
private static func loadFromSecurity(key: String) -> Data? {
|
||||
let query = [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrAccount as String: key,
|
||||
kSecReturnData as String: kCFBooleanTrue!,
|
||||
kSecMatchLimit as String: kSecMatchLimitOne
|
||||
] as [String: Any]
|
||||
|
||||
var dataTypeRef: AnyObject?
|
||||
let status = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
|
||||
|
||||
if status == errSecSuccess {
|
||||
return dataTypeRef as? Data
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 生成uuid
|
||||
private static func generatorUserId() -> String {
|
||||
if let uuid = UIDevice.current.identifierForVendor?.uuidString {
|
||||
return uuid.lowercased()
|
||||
} else {
|
||||
return UUID().uuidString.lowercased()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -26,12 +26,4 @@ struct Utils {
|
||||
return attributedString.string
|
||||
}
|
||||
|
||||
static func defaultUserId() -> String {
|
||||
if let uuid = UIDevice.current.identifierForVendor?.uuidString {
|
||||
return uuid.lowercased()
|
||||
} else {
|
||||
return UUID().uuidString.lowercased()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
import SwiftUI
|
||||
|
||||
struct DetailView: View {
|
||||
@AppStorage("userId") private var userId: String = Utils.defaultUserId()
|
||||
@Environment(\.userId) private var userId
|
||||
|
||||
@State var detailModel = DetailModel()
|
||||
@State var showAllSummary: Bool = false
|
||||
|
||||
@ -9,7 +9,7 @@ import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct FollowListView: View {
|
||||
@AppStorage("userId") private var userId: String = Utils.defaultUserId()
|
||||
@Environment(\.userId) private var userId
|
||||
@State var followModel = FollowListModel()
|
||||
@State var isMoreLoading: Bool = false
|
||||
|
||||
|
||||
@ -62,7 +62,7 @@ final class DateNavModel {
|
||||
}
|
||||
|
||||
struct DateNavView: View {
|
||||
@AppStorage("userId") private var userId: String = Utils.defaultUserId()
|
||||
@Environment(\.userId) private var userId
|
||||
@State var navModel = DateNavModel()
|
||||
|
||||
@Binding var selectGroupId: String
|
||||
|
||||
@ -11,7 +11,7 @@ import Combine
|
||||
struct IndexMainView: View {
|
||||
@Environment(\.modelContext) private var modelContext
|
||||
@EnvironmentObject var appNavigation: AppNavigation
|
||||
@AppStorage("userId") private var userId: String = Utils.defaultUserId()
|
||||
@Environment(\.userId) private var userId
|
||||
|
||||
@State var indexModel = IndexModel()
|
||||
@State private var scrollID: IndexModel.ScrollTarget? = nil
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
import SwiftUI
|
||||
|
||||
struct ListView: View {
|
||||
@AppStorage("userId") private var userId: String = Utils.defaultUserId()
|
||||
@Environment(\.userId) private var userId
|
||||
@State var detailModel = ListModel()
|
||||
|
||||
let id: Int
|
||||
|
||||
@ -10,7 +10,7 @@ import SwiftData
|
||||
|
||||
struct SearchView: View {
|
||||
@Environment(\.modelContext) var modelContext
|
||||
@AppStorage("userId") private var userId: String = Utils.defaultUserId()
|
||||
@Environment(\.userId) private var userId
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@State var searchText: String = ""
|
||||
@ -19,6 +19,12 @@ struct SearchView: View {
|
||||
@FocusState private var isFocused: Bool
|
||||
@State private var isSearching = false
|
||||
|
||||
// 是否显示搜索结果
|
||||
@State private var showSearchResult: Bool = false
|
||||
|
||||
// 控制是否需要显示键盘
|
||||
@State private var showKeyboard: Bool = true
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 12) {
|
||||
HStack(spacing: 12) {
|
||||
@ -74,6 +80,7 @@ struct SearchView: View {
|
||||
.frame(height: 50)
|
||||
.padding(.top, 12)
|
||||
|
||||
if showSearchResult {
|
||||
if searchModel.dramaGroups.isEmpty {
|
||||
Spacer()
|
||||
Text("什么都没有找到")
|
||||
@ -89,7 +96,20 @@ struct SearchView: View {
|
||||
}
|
||||
.padding(.top, 8)
|
||||
}
|
||||
.onTapGesture {
|
||||
isFocused = false
|
||||
}
|
||||
.simultaneousGesture(
|
||||
DragGesture().onChanged{ _ in
|
||||
isFocused = false
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// 预留用来显示热搜词
|
||||
Spacer()
|
||||
}
|
||||
|
||||
}
|
||||
.navigationTitle("")
|
||||
.navigationBarBackButtonHidden(true)
|
||||
@ -97,18 +117,28 @@ struct SearchView: View {
|
||||
.ignoresSafeArea(.keyboard, edges: .bottom) // 避免键盘遮挡
|
||||
.onAppear {
|
||||
// 避免过早触发系统输入错误
|
||||
if showKeyboard {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
isFocused = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func doSearch() {
|
||||
let trimmed = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if !trimmed.isEmpty {
|
||||
Task.detached {
|
||||
await searchModel.search(userId: userId, name: trimmed)
|
||||
DispatchQueue.main.async {
|
||||
// 显示搜索结果
|
||||
self.showSearchResult = true
|
||||
}
|
||||
}
|
||||
|
||||
// 其他情况下不自动弹出键盘
|
||||
self.showKeyboard = false
|
||||
|
||||
// 可选:添加历史记录
|
||||
// let history = SearchHistory(keyword: trimmed, timestamp: Date())
|
||||
// modelContext.insert(history)
|
||||
|
||||
@ -33,11 +33,8 @@ struct dimensionhubApp: App {
|
||||
}()
|
||||
|
||||
init() {
|
||||
if let userId = UserDefaults.standard.string(forKey: "userId") {
|
||||
let userId = KeychainHelper.getPersistentUserId()
|
||||
print("user_id is: \(userId)")
|
||||
} else {
|
||||
UserDefaults.standard.set(Utils.defaultUserId(), forKey: "userId")
|
||||
}
|
||||
}
|
||||
|
||||
var body: some Scene {
|
||||
@ -54,6 +51,7 @@ struct dimensionhubApp: App {
|
||||
SearchView()
|
||||
}
|
||||
}
|
||||
.environment(\.userId, KeychainHelper.getPersistentUserId())
|
||||
}
|
||||
.navigationViewStyle(.stack)
|
||||
.tint(.black)
|
||||
@ -79,20 +77,20 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||
UNUserNotificationCenter.current().delegate = self
|
||||
// 请求通知权限
|
||||
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) {granted, error in
|
||||
print("通知权限 granted: \(granted)")
|
||||
NSLog("通知权限 granted: \(granted)")
|
||||
guard granted else {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取通知设置
|
||||
UNUserNotificationCenter.current().getNotificationSettings { settings in
|
||||
print("通知设置: \(settings)")
|
||||
NSLog("通知设置: \(settings)")
|
||||
|
||||
guard settings.authorizationStatus == .authorized else {
|
||||
return
|
||||
}
|
||||
|
||||
print("通知设置: authorized!!!!")
|
||||
NSLog("通知设置: authorized!!!!")
|
||||
|
||||
DispatchQueue.main.async {
|
||||
// 注册远程通知
|
||||
@ -108,12 +106,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||
let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
|
||||
let token = tokenParts.joined()
|
||||
|
||||
print("Device Token: \(token)")
|
||||
NSLog("Device Token: \(token)")
|
||||
// 将 deviceToken 发送给你的服务器
|
||||
Task {
|
||||
guard let userId = UserDefaults.standard.string(forKey: "userId") else {
|
||||
return
|
||||
}
|
||||
let userId = KeychainHelper.getPersistentUserId()
|
||||
|
||||
let sendResult = await API.sendDeviceTokenToServer(userId: userId, token: token, as: String.self)
|
||||
switch sendResult {
|
||||
@ -127,7 +123,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||
|
||||
// 注册远程通知失败
|
||||
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
|
||||
print("注册远程通知失败: \(error)")
|
||||
NSLog("注册远程通知失败: \(error)")
|
||||
}
|
||||
|
||||
// 收到远程通知(App 在前台)
|
||||
@ -191,8 +187,20 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||
|
||||
private func handleDeepLink(_ link: String) {
|
||||
// 实现深度链接处理逻辑
|
||||
print("处理深度链接: \(link)")
|
||||
NSLog("处理深度链接: \(link)")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// userId通过环境变量传递
|
||||
|
||||
struct UserId: EnvironmentKey {
|
||||
static let defaultValue: String = KeychainHelper.getPersistentUserId()
|
||||
}
|
||||
|
||||
extension EnvironmentValues {
|
||||
var userId: String {
|
||||
get { self[UserId.self] }
|
||||
set { self[UserId.self] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user