持久化用户id

This commit is contained in:
anlicheng 2025-06-26 17:36:10 +08:00
parent b95420ef74
commit 6323b1d2c9
9 changed files with 169 additions and 42 deletions

View 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()
}
}
}

View File

@ -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()
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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 }
}
}