feat: token
This commit is contained in:
parent
be25d07d83
commit
fdd2715ec8
Binary file not shown.
49
wake/Models/AuthState.swift
Normal file
49
wake/Models/AuthState.swift
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
/// 管理用户认证状态的类
|
||||||
|
public class AuthState: ObservableObject {
|
||||||
|
@Published public var isAuthenticated: Bool = false {
|
||||||
|
didSet {
|
||||||
|
print("🔔 认证状态变更: \(isAuthenticated ? "已登录" : "已登出")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Published public var isLoading = false
|
||||||
|
@Published public var errorMessage: String?
|
||||||
|
@Published public var user: User?
|
||||||
|
|
||||||
|
// 单例模式
|
||||||
|
public static let shared = AuthState()
|
||||||
|
|
||||||
|
private init() {}
|
||||||
|
|
||||||
|
/// 登录成功时调用
|
||||||
|
public func login(user: User? = nil) {
|
||||||
|
if let user = user {
|
||||||
|
self.user = user
|
||||||
|
}
|
||||||
|
isAuthenticated = true
|
||||||
|
errorMessage = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 登出时调用
|
||||||
|
public func logout() {
|
||||||
|
print("👋 用户登出")
|
||||||
|
user = nil
|
||||||
|
isAuthenticated = false
|
||||||
|
|
||||||
|
// 清除用户数据
|
||||||
|
TokenManager.shared.clearTokens()
|
||||||
|
UserDefaults.standard.removeObject(forKey: "lastLoginUser")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 更新加载状态
|
||||||
|
public func setLoading(_ loading: Bool) {
|
||||||
|
isLoading = loading
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 设置错误信息
|
||||||
|
public func setError(_ message: String) {
|
||||||
|
errorMessage = message
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,12 +7,10 @@ public enum APIConfig {
|
|||||||
<<<<<<< HEAD
|
<<<<<<< HEAD
|
||||||
public static let baseURL = "https://api-dev.memorywake.com:31274/api/v1"
|
public static let baseURL = "https://api-dev.memorywake.com:31274/api/v1"
|
||||||
|
|
||||||
/// 认证 token - 从 Keychain 中获取
|
/// 获取认证token
|
||||||
public static var authToken: String {
|
public static var authToken: String {
|
||||||
let token = KeychainHelper.getAccessToken() ?? ""
|
let token = KeychainHelper.getAccessToken() ?? ""
|
||||||
if !token.isEmpty {
|
if token.isEmpty {
|
||||||
print("🔑 [APIConfig] 当前访问令牌: \(token.prefix(10))...") // 只打印前10个字符,避免敏感信息完全暴露
|
|
||||||
} else {
|
|
||||||
print("⚠️ [APIConfig] 未找到访问令牌")
|
print("⚠️ [APIConfig] 未找到访问令牌")
|
||||||
}
|
}
|
||||||
return token
|
return token
|
||||||
@ -42,11 +40,17 @@ public enum APIConfig {
|
|||||||
>>>>>>> 1814789 (feat: 登录接口联调)
|
>>>>>>> 1814789 (feat: 登录接口联调)
|
||||||
/// 认证请求头
|
/// 认证请求头
|
||||||
public static var authHeaders: [String: String] {
|
public static var authHeaders: [String: String] {
|
||||||
return [
|
let token = authToken
|
||||||
"Authorization": "Bearer \(authToken)",
|
var headers = [
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Accept": "application/json"
|
"Accept": "application/json"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if !token.isEmpty {
|
||||||
|
headers["Authorization"] = "Bearer \(token)"
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers
|
||||||
}
|
}
|
||||||
<<<<<<< HEAD
|
<<<<<<< HEAD
|
||||||
<<<<<<< HEAD
|
<<<<<<< HEAD
|
||||||
|
|||||||
@ -1,34 +0,0 @@
|
|||||||
import SwiftUI
|
|
||||||
|
|
||||||
class Network: ObservableObject {
|
|
||||||
@Published var users: [User] = []
|
|
||||||
|
|
||||||
func getUsers() {
|
|
||||||
guard let url = URL(string: "http://192.168.31.156:31646/api/iam/login/password-login") else { fatalError("Missing URL") }
|
|
||||||
|
|
||||||
let urlRequest = URLRequest(url: url)
|
|
||||||
|
|
||||||
let dataTask = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
|
|
||||||
if let error = error {
|
|
||||||
print("Request error: ", error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let response = response as? HTTPURLResponse else { return }
|
|
||||||
|
|
||||||
if response.statusCode == 200 {
|
|
||||||
guard let data = data else { return }
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
do {
|
|
||||||
let decodedUsers = try JSONDecoder().decode([User].self, from: data)
|
|
||||||
self.users = decodedUsers
|
|
||||||
} catch let error {
|
|
||||||
print("Error decoding: ", error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dataTask.resume()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
207
wake/Utils/NetworkService.swift
Normal file
207
wake/Utils/NetworkService.swift
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum NetworkError: Error {
|
||||||
|
case invalidURL
|
||||||
|
case noData
|
||||||
|
case decodingError(Error)
|
||||||
|
case serverError(String)
|
||||||
|
case unauthorized
|
||||||
|
case other(Error)
|
||||||
|
case networkError(Error)
|
||||||
|
case unknownError(Error)
|
||||||
|
|
||||||
|
var localizedDescription: String {
|
||||||
|
switch self {
|
||||||
|
case .invalidURL:
|
||||||
|
return "无效的URL"
|
||||||
|
case .noData:
|
||||||
|
return "没有收到数据"
|
||||||
|
case .decodingError(let error):
|
||||||
|
return "数据解析错误: \(error.localizedDescription)"
|
||||||
|
case .serverError(let message):
|
||||||
|
return "服务器错误: \(message)"
|
||||||
|
case .unauthorized:
|
||||||
|
return "未授权,请重新登录"
|
||||||
|
case .other(let error):
|
||||||
|
return error.localizedDescription
|
||||||
|
case .networkError(let error):
|
||||||
|
return "网络请求错误: \(error.localizedDescription)"
|
||||||
|
case .unknownError(let error):
|
||||||
|
return "未知错误: \(error.localizedDescription)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NetworkService {
|
||||||
|
static let shared = NetworkService()
|
||||||
|
|
||||||
|
// 默认请求头
|
||||||
|
private let defaultHeaders: [String: String] = [
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json"
|
||||||
|
]
|
||||||
|
|
||||||
|
private init() {}
|
||||||
|
|
||||||
|
// MARK: - 基础请求方法
|
||||||
|
private func request<T: Decodable>(
|
||||||
|
_ method: String,
|
||||||
|
path: String,
|
||||||
|
parameters: [String: Any]? = nil,
|
||||||
|
headers: [String: String]? = nil,
|
||||||
|
completion: @escaping (Result<T, NetworkError>) -> Void
|
||||||
|
) {
|
||||||
|
// 构建URL
|
||||||
|
guard let url = URL(string: APIConfig.baseURL + path) else {
|
||||||
|
completion(.failure(.invalidURL))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建请求
|
||||||
|
var request = URLRequest(url: url)
|
||||||
|
request.httpMethod = method
|
||||||
|
|
||||||
|
// 设置请求头 - 合并默认头、认证头和自定义头
|
||||||
|
defaultHeaders.forEach { key, value in
|
||||||
|
request.setValue(value, forHTTPHeaderField: key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加认证头
|
||||||
|
APIConfig.authHeaders.forEach { key, value in
|
||||||
|
request.setValue(value, forHTTPHeaderField: key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加自定义头(如果提供)
|
||||||
|
headers?.forEach { key, value in
|
||||||
|
request.setValue(value, forHTTPHeaderField: key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置请求体(如果是POST/PUT请求)
|
||||||
|
if let parameters = parameters, (method == "POST" || method == "PUT") {
|
||||||
|
do {
|
||||||
|
request.httpBody = try JSONSerialization.data(withJSONObject: parameters)
|
||||||
|
} catch {
|
||||||
|
completion(.failure(.other(error)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打印请求信息(调试用)
|
||||||
|
print("🌐 [Network] \(method) \(url.absoluteString)")
|
||||||
|
if let headers = request.allHTTPHeaderFields {
|
||||||
|
print("📤 Headers: \(headers)")
|
||||||
|
}
|
||||||
|
if let body = request.httpBody, let bodyString = String(data: body, encoding: .utf8) {
|
||||||
|
print("📦 Body: \(bodyString)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建任务
|
||||||
|
let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
|
||||||
|
// 处理响应
|
||||||
|
self?.handleResponse(data: data, response: response, error: error, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始请求
|
||||||
|
task.resume()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handleResponse<T: Decodable>(
|
||||||
|
data: Data?,
|
||||||
|
response: URLResponse?,
|
||||||
|
error: Error?,
|
||||||
|
completion: @escaping (Result<T, NetworkError>) -> Void
|
||||||
|
) {
|
||||||
|
// 打印响应信息(调试用)
|
||||||
|
if let httpResponse = response as? HTTPURLResponse {
|
||||||
|
print("📥 [Network] Status: \(httpResponse.statusCode) \(HTTPURLResponse.localizedString(forStatusCode: httpResponse.statusCode))")
|
||||||
|
if let headers = httpResponse.allHeaderFields as? [String: Any] {
|
||||||
|
print("📥 Headers: \(headers)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查状态码
|
||||||
|
if !(200...299).contains(httpResponse.statusCode) {
|
||||||
|
print("❌ [Network] 请求失败,状态码: \(httpResponse.statusCode)")
|
||||||
|
if let data = data, let errorResponse = String(data: data, encoding: .utf8) {
|
||||||
|
print("❌ [Network] 错误响应: \(errorResponse)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理网络错误
|
||||||
|
if let error = error {
|
||||||
|
print("❌ [Network] 网络请求错误: \(error.localizedDescription)")
|
||||||
|
completion(.failure(.networkError(error)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查数据是否存在
|
||||||
|
guard let data = data else {
|
||||||
|
print("❌ [Network] 没有收到数据")
|
||||||
|
completion(.failure(.noData))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打印响应数据(调试用)
|
||||||
|
if let responseString = String(data: data, encoding: .utf8) {
|
||||||
|
print("📥 [Network] 响应数据: \(responseString)")
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
// 解析JSON数据
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
let result = try decoder.decode(T.self, from: data)
|
||||||
|
completion(.success(result))
|
||||||
|
} catch let decodingError as DecodingError {
|
||||||
|
print("❌ [Network] JSON解析失败: \(decodingError.localizedDescription)")
|
||||||
|
if let dataString = String(data: data, encoding: .utf8) {
|
||||||
|
print("📋 [Network] 原始响应: \(dataString)")
|
||||||
|
}
|
||||||
|
completion(.failure(.decodingError(decodingError)))
|
||||||
|
} catch {
|
||||||
|
print("❌ [Network] 未知错误: \(error.localizedDescription)")
|
||||||
|
completion(.failure(.unknownError(error)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 公共方法
|
||||||
|
|
||||||
|
/// GET 请求
|
||||||
|
func get<T: Decodable>(
|
||||||
|
path: String,
|
||||||
|
parameters: [String: Any]? = nil,
|
||||||
|
headers: [String: String]? = nil,
|
||||||
|
completion: @escaping (Result<T, NetworkError>) -> Void
|
||||||
|
) {
|
||||||
|
request("GET", path: path, parameters: parameters, headers: headers, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// POST 请求
|
||||||
|
func post<T: Decodable>(
|
||||||
|
path: String,
|
||||||
|
parameters: [String: Any]? = nil,
|
||||||
|
headers: [String: String]? = nil,
|
||||||
|
completion: @escaping (Result<T, NetworkError>) -> Void
|
||||||
|
) {
|
||||||
|
request("POST", path: path, parameters: parameters, headers: headers, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// DELETE 请求
|
||||||
|
func delete<T: Decodable>(
|
||||||
|
path: String,
|
||||||
|
parameters: [String: Any]? = nil,
|
||||||
|
headers: [String: String]? = nil,
|
||||||
|
completion: @escaping (Result<T, NetworkError>) -> Void
|
||||||
|
) {
|
||||||
|
request("DELETE", path: path, parameters: parameters, headers: headers, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PUT 请求
|
||||||
|
func put<T: Decodable>(
|
||||||
|
path: String,
|
||||||
|
parameters: [String: Any]? = nil,
|
||||||
|
headers: [String: String]? = nil,
|
||||||
|
completion: @escaping (Result<T, NetworkError>) -> Void
|
||||||
|
) {
|
||||||
|
request("PUT", path: path, parameters: parameters, headers: headers, completion: completion)
|
||||||
|
}
|
||||||
|
}
|
||||||
424
wake/Utils/TokenManager.swift
Normal file
424
wake/Utils/TokenManager.swift
Normal file
@ -0,0 +1,424 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Token管理器
|
||||||
|
/// 负责管理应用的认证令牌,包括验证、刷新和过期处理
|
||||||
|
class TokenManager {
|
||||||
|
/// 单例实例
|
||||||
|
static let shared = TokenManager()
|
||||||
|
|
||||||
|
/// token有效期阈值(秒),在token即将过期前进行刷新
|
||||||
|
/// 例如:设置为300表示在token过期前5分钟开始刷新
|
||||||
|
private let tokenValidityThreshold: TimeInterval = 300
|
||||||
|
|
||||||
|
/// 私有化初始化方法,确保单例模式
|
||||||
|
private init() {}
|
||||||
|
|
||||||
|
// MARK: - Token 状态检查
|
||||||
|
|
||||||
|
/// 检查是否存在有效的访问令牌
|
||||||
|
var hasToken: Bool {
|
||||||
|
return KeychainHelper.getAccessToken()?.isEmpty == false
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Token 验证
|
||||||
|
|
||||||
|
/// 验证并刷新token(如果需要)
|
||||||
|
/// - 检查token是否存在
|
||||||
|
/// - 检查token是否有效
|
||||||
|
/// - 在token即将过期时自动刷新
|
||||||
|
/// - Parameter completion: 完成回调,返回验证/刷新结果
|
||||||
|
/// - isValid: token是否有效
|
||||||
|
/// - error: 错误信息(如果有)
|
||||||
|
func validateAndRefreshTokenIfNeeded(completion: @escaping (Bool, Error?) -> Void) {
|
||||||
|
// 1. 检查token是否存在
|
||||||
|
guard let token = KeychainHelper.getAccessToken(), !token.isEmpty else {
|
||||||
|
// token不存在,返回未授权错误
|
||||||
|
let error = NSError(
|
||||||
|
domain: "TokenManager",
|
||||||
|
code: 401,
|
||||||
|
userInfo: [NSLocalizedDescriptionKey: "未找到访问令牌"]
|
||||||
|
)
|
||||||
|
completion(false, error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查token是否有效
|
||||||
|
if isTokenValid(token) {
|
||||||
|
// token有效,直接返回成功
|
||||||
|
completion(true, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. token无效或即将过期,尝试刷新
|
||||||
|
refreshToken { [weak self] success, error in
|
||||||
|
if success {
|
||||||
|
// 刷新成功,返回成功
|
||||||
|
completion(true, nil)
|
||||||
|
} else {
|
||||||
|
// 刷新失败,返回错误信息
|
||||||
|
let finalError = error ?? NSError(
|
||||||
|
domain: "TokenManager",
|
||||||
|
code: 401,
|
||||||
|
userInfo: [NSLocalizedDescriptionKey: "Token刷新失败"]
|
||||||
|
)
|
||||||
|
completion(false, finalError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 检查token是否有效
|
||||||
|
/// - Parameter token: 要检查的token字符串
|
||||||
|
/// - Returns: 如果token有效返回true,否则返回false
|
||||||
|
///
|
||||||
|
/// 该方法会检查token的有效性,包括检查token是否为空、是否过期以及通过网络请求验证token。
|
||||||
|
///
|
||||||
|
/// - Note: 该方法会打印一些调试信息,包括token验证开始、token过期时间等。
|
||||||
|
public func isTokenValid(_ token: String) -> Bool {
|
||||||
|
print("🔍 TokenManager: 开始验证token...")
|
||||||
|
|
||||||
|
// 1. 基础验证:检查token是否为空
|
||||||
|
guard !token.isEmpty else {
|
||||||
|
print("❌ TokenManager: Token为空")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查token是否过期(如果可能)
|
||||||
|
if let expiryDate = getTokenExpiryDate(token) {
|
||||||
|
print("⏰ TokenManager: Token过期时间: \(expiryDate)")
|
||||||
|
if Date() > expiryDate {
|
||||||
|
print("❌ TokenManager: Token已过期")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 创建信号量用于同步网络请求
|
||||||
|
let semaphore = DispatchSemaphore(value: 0)
|
||||||
|
var isValid = false
|
||||||
|
var requestCompleted = false
|
||||||
|
|
||||||
|
print("🌐 TokenManager: 发送验证请求到服务器...")
|
||||||
|
|
||||||
|
// 4. 发送验证请求
|
||||||
|
let task = URLSession.shared.dataTask(with: createValidationRequest(token: token)) { data, response, error in
|
||||||
|
defer {
|
||||||
|
requestCompleted = true
|
||||||
|
semaphore.signal()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查网络错误
|
||||||
|
if let error = error {
|
||||||
|
print("❌ TokenManager: 验证请求错误: \(error.localizedDescription)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查响应状态码
|
||||||
|
guard let httpResponse = response as? HTTPURLResponse else {
|
||||||
|
print("❌ TokenManager: 无效的服务器响应")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
print("📡 TokenManager: 服务器响应状态码: \(httpResponse.statusCode)")
|
||||||
|
|
||||||
|
// 检查状态码
|
||||||
|
guard (200...299).contains(httpResponse.statusCode) else {
|
||||||
|
print("❌ TokenManager: 服务器返回错误状态码: \(httpResponse.statusCode)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有数据
|
||||||
|
if let data = data, !data.isEmpty {
|
||||||
|
do {
|
||||||
|
// 尝试解析响应数据
|
||||||
|
let response = try JSONDecoder().decode(IdentityCheckResponse.self, from: data)
|
||||||
|
isValid = response.isValid
|
||||||
|
print("✅ TokenManager: Token验证\(isValid ? "成功" : "失败")")
|
||||||
|
} catch {
|
||||||
|
print("❌ TokenManager: 解析响应数据失败: \(error.localizedDescription)")
|
||||||
|
// 如果解析失败但状态码是200,我们假设token是有效的
|
||||||
|
isValid = true
|
||||||
|
print("ℹ️ TokenManager: 状态码200,假设token有效")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果没有返回数据但状态码是200,我们假设token是有效的
|
||||||
|
print("ℹ️ TokenManager: 没有返回数据,但状态码为200,假设token有效")
|
||||||
|
isValid = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task.resume()
|
||||||
|
|
||||||
|
// 5. 设置超时时间(10秒)
|
||||||
|
let timeoutResult = semaphore.wait(timeout: .now() + 15)
|
||||||
|
|
||||||
|
// 检查是否超时
|
||||||
|
if !requestCompleted && timeoutResult == .timedOut {
|
||||||
|
print("⚠️ TokenManager: 验证请求超时")
|
||||||
|
task.cancel()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValid
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 创建验证请求
|
||||||
|
private func createValidationRequest(token: String) -> URLRequest {
|
||||||
|
let url = URL(string: APIConfig.baseURL + "/iam/identity-check")!
|
||||||
|
var request = URLRequest(url: url)
|
||||||
|
request.httpMethod = "POST"
|
||||||
|
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
||||||
|
request.setValue("application/json", forHTTPHeaderField: "Accept")
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 从token中提取过期时间(示例实现)
|
||||||
|
private func getTokenExpiryDate(_ token: String) -> Date? {
|
||||||
|
// 这里需要根据实际的JWT或其他token格式来解析过期时间
|
||||||
|
// 以下是JWT token的示例解析
|
||||||
|
let parts = token.components(separatedBy: ".")
|
||||||
|
guard parts.count > 1, let payloadData = base64UrlDecode(parts[1]) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
if let payload = try JSONSerialization.jsonObject(with: payloadData) as? [String: Any],
|
||||||
|
let exp = payload["exp"] as? TimeInterval {
|
||||||
|
return Date(timeIntervalSince1970: exp)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
print("❌ TokenManager: 解析token过期时间失败: \(error.localizedDescription)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func base64UrlDecode(_ base64Url: String) -> Data? {
|
||||||
|
var base64 = base64Url
|
||||||
|
.replacingOccurrences(of: "-", with: "+")
|
||||||
|
.replacingOccurrences(of: "_", with: "/")
|
||||||
|
|
||||||
|
// 添加必要的填充
|
||||||
|
let length = Double(base64.lengthOfBytes(using: .utf8))
|
||||||
|
let requiredLength = 4 * ceil(length / 4.0)
|
||||||
|
let paddingLength = requiredLength - length
|
||||||
|
if paddingLength > 0 {
|
||||||
|
let padding = "".padding(toLength: Int(paddingLength), withPad: "=", startingAt: 0)
|
||||||
|
base64 += padding
|
||||||
|
}
|
||||||
|
|
||||||
|
return Data(base64Encoded: base64)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 刷新token
|
||||||
|
/// - Parameter completion: 刷新完成回调
|
||||||
|
/// - success: 是否刷新成功
|
||||||
|
/// - error: 错误信息(如果有)
|
||||||
|
private func refreshToken(completion: @escaping (Bool, Error?) -> Void) {
|
||||||
|
// 获取刷新令牌
|
||||||
|
guard let refreshToken = KeychainHelper.getRefreshToken(), !refreshToken.isEmpty else {
|
||||||
|
// 没有可用的刷新令牌
|
||||||
|
let error = NSError(
|
||||||
|
domain: "TokenManager",
|
||||||
|
code: 401,
|
||||||
|
userInfo: [NSLocalizedDescriptionKey: "未找到刷新令牌"]
|
||||||
|
)
|
||||||
|
completion(false, error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 准备刷新请求参数
|
||||||
|
let parameters: [String: Any] = [
|
||||||
|
"refresh_token": refreshToken,
|
||||||
|
"grant_type": "refresh_token"
|
||||||
|
]
|
||||||
|
|
||||||
|
// 发送刷新请求
|
||||||
|
NetworkService.shared.postWithToken(path: "/v1/iam/access-token-refresh", parameters: parameters) {
|
||||||
|
(result: Result<TokenResponse, NetworkError>) in
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
case .success(let tokenResponse):
|
||||||
|
// 1. 保存新的访问令牌
|
||||||
|
KeychainHelper.saveAccessToken(tokenResponse.accessToken)
|
||||||
|
|
||||||
|
// 2. 如果返回了新的刷新令牌,也保存起来
|
||||||
|
if let newRefreshToken = tokenResponse.refreshToken {
|
||||||
|
KeychainHelper.saveRefreshToken(newRefreshToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
print("✅ Token刷新成功")
|
||||||
|
completion(true, nil)
|
||||||
|
|
||||||
|
case .failure(let error):
|
||||||
|
print("❌ Token刷新失败: \(error.localizedDescription)")
|
||||||
|
|
||||||
|
// 刷新失败,清除本地token,需要用户重新登录
|
||||||
|
KeychainHelper.clearTokens()
|
||||||
|
|
||||||
|
completion(false, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 清除所有存储的 token
|
||||||
|
func clearTokens() {
|
||||||
|
print("🗑️ TokenManager: 清除所有 token")
|
||||||
|
KeychainHelper.clearTokens()
|
||||||
|
// 清除其他与 token 相关的存储
|
||||||
|
UserDefaults.standard.removeObject(forKey: "tokenExpiryDate")
|
||||||
|
UserDefaults.standard.synchronize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Token响应模型
|
||||||
|
/// 用于解析token刷新接口的响应数据
|
||||||
|
private struct TokenResponse: Codable {
|
||||||
|
/// 访问令牌
|
||||||
|
let accessToken: String
|
||||||
|
|
||||||
|
/// 刷新令牌(可选)
|
||||||
|
let refreshToken: String?
|
||||||
|
|
||||||
|
/// 过期时间(秒)
|
||||||
|
let expiresIn: TimeInterval?
|
||||||
|
|
||||||
|
/// 令牌类型(如:Bearer)
|
||||||
|
let tokenType: String?
|
||||||
|
|
||||||
|
// 使用CodingKeys自定义键名映射
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case accessToken = "access_token"
|
||||||
|
case refreshToken = "refresh_token"
|
||||||
|
case expiresIn = "expires_in"
|
||||||
|
case tokenType = "token_type"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 身份验证响应模型
|
||||||
|
/// 用于解析身份验证接口的响应数据
|
||||||
|
private struct IdentityCheckResponse: Codable {
|
||||||
|
/// 是否有效
|
||||||
|
let isValid: Bool
|
||||||
|
|
||||||
|
/// 用户ID(可选)
|
||||||
|
let userId: String?
|
||||||
|
|
||||||
|
/// 过期时间(可选)
|
||||||
|
let expiresAt: Date?
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case isValid = "is_valid"
|
||||||
|
case userId = "user_id"
|
||||||
|
case expiresAt = "expires_at"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - NetworkService扩展
|
||||||
|
/// 为NetworkService添加带token验证的请求方法
|
||||||
|
extension NetworkService {
|
||||||
|
// MARK: - 核心请求方法
|
||||||
|
|
||||||
|
/// 带token验证的网络请求
|
||||||
|
/// - Parameters:
|
||||||
|
/// - method: HTTP方法(GET/POST/PUT/DELETE等)
|
||||||
|
/// - path: 接口路径
|
||||||
|
/// - parameters: 请求参数
|
||||||
|
/// - headers: 自定义请求头
|
||||||
|
/// - completion: 完成回调
|
||||||
|
func requestWithToken<T: Decodable>(
|
||||||
|
_ method: String,
|
||||||
|
path: String,
|
||||||
|
parameters: [String: Any]? = nil,
|
||||||
|
headers: [String: String]? = nil,
|
||||||
|
completion: @escaping (Result<T, NetworkError>) -> Void
|
||||||
|
) {
|
||||||
|
// 1. 验证并刷新token
|
||||||
|
TokenManager.shared.validateAndRefreshTokenIfNeeded { [weak self] isValid, error in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
if isValid {
|
||||||
|
// 2. token有效,继续发送原始请求
|
||||||
|
switch method.uppercased() {
|
||||||
|
case "GET":
|
||||||
|
self.get(path: path, parameters: parameters, headers: headers, completion: completion)
|
||||||
|
case "POST":
|
||||||
|
self.post(path: path, parameters: parameters, headers: headers, completion: completion)
|
||||||
|
case "PUT":
|
||||||
|
self.put(path: path, parameters: parameters, headers: headers, completion: completion)
|
||||||
|
case "DELETE":
|
||||||
|
self.delete(path: path, parameters: parameters, headers: headers, completion: completion)
|
||||||
|
default:
|
||||||
|
let error = NSError(
|
||||||
|
domain: "NetworkService",
|
||||||
|
code: 400,
|
||||||
|
userInfo: [NSLocalizedDescriptionKey: "不支持的HTTP方法: \(method)"]
|
||||||
|
)
|
||||||
|
completion(.failure(.other(error)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 3. token无效,返回未授权错误
|
||||||
|
let error = error ?? NSError(
|
||||||
|
domain: "NetworkService",
|
||||||
|
code: 401,
|
||||||
|
userInfo: [NSLocalizedDescriptionKey: "未授权访问"]
|
||||||
|
)
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
completion(.failure(.unauthorized))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 发送登出通知,让应用处理未授权情况
|
||||||
|
NotificationCenter.default.post(name: .userDidLogout, object: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 便捷方法
|
||||||
|
|
||||||
|
/// 带token验证的GET请求
|
||||||
|
func getWithToken<T: Decodable>(
|
||||||
|
path: String,
|
||||||
|
parameters: [String: Any]? = nil,
|
||||||
|
headers: [String: String]? = nil,
|
||||||
|
completion: @escaping (Result<T, NetworkError>) -> Void
|
||||||
|
) {
|
||||||
|
requestWithToken("GET", path: path, parameters: parameters, headers: headers, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 带token验证的POST请求
|
||||||
|
func postWithToken<T: Decodable>(
|
||||||
|
path: String,
|
||||||
|
parameters: [String: Any]? = nil,
|
||||||
|
headers: [String: String]? = nil,
|
||||||
|
completion: @escaping (Result<T, NetworkError>) -> Void
|
||||||
|
) {
|
||||||
|
requestWithToken("POST", path: path, parameters: parameters, headers: headers, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 带token验证的PUT请求
|
||||||
|
func putWithToken<T: Decodable>(
|
||||||
|
path: String,
|
||||||
|
parameters: [String: Any]? = nil,
|
||||||
|
headers: [String: String]? = nil,
|
||||||
|
completion: @escaping (Result<T, NetworkError>) -> Void
|
||||||
|
) {
|
||||||
|
requestWithToken("PUT", path: path, parameters: parameters, headers: headers, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 带token验证的DELETE请求
|
||||||
|
func deleteWithToken<T: Decodable>(
|
||||||
|
path: String,
|
||||||
|
parameters: [String: Any]? = nil,
|
||||||
|
headers: [String: String]? = nil,
|
||||||
|
completion: @escaping (Result<T, NetworkError>) -> Void
|
||||||
|
) {
|
||||||
|
requestWithToken("DELETE", path: path, parameters: parameters, headers: headers, completion: completion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 通知名称
|
||||||
|
/// 定义应用中使用的通知名称
|
||||||
|
extension Notification.Name {
|
||||||
|
/// 用户登出通知
|
||||||
|
/// 当token失效或用户主动登出时发送
|
||||||
|
static let userDidLogout = Notification.Name("UserDidLogoutNotification")
|
||||||
|
}
|
||||||
@ -1,31 +1,61 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct User: Identifiable, Decodable {
|
public struct User: Identifiable, Decodable {
|
||||||
var id: Int
|
public var id: Int
|
||||||
var name: String
|
public var name: String
|
||||||
var username: String
|
public var username: String
|
||||||
var email: String
|
public var email: String
|
||||||
var address: Address
|
public var address: Address
|
||||||
var phone: String
|
public var phone: String
|
||||||
var website: String
|
public var website: String
|
||||||
var company: Company
|
public var company: Company
|
||||||
|
|
||||||
struct Address: Decodable {
|
public init(id: Int, name: String, username: String, email: String, address: Address, phone: String, website: String, company: Company) {
|
||||||
var street: String
|
self.id = id
|
||||||
var suite: String
|
self.name = name
|
||||||
var city: String
|
self.username = username
|
||||||
var zipcode: String
|
self.email = email
|
||||||
var geo: Geo
|
self.address = address
|
||||||
|
self.phone = phone
|
||||||
|
self.website = website
|
||||||
|
self.company = company
|
||||||
|
}
|
||||||
|
|
||||||
struct Geo: Decodable {
|
public struct Address: Decodable {
|
||||||
var lat: String
|
public var street: String
|
||||||
var lng: String
|
public var suite: String
|
||||||
|
public var city: String
|
||||||
|
public var zipcode: String
|
||||||
|
public var geo: Geo
|
||||||
|
|
||||||
|
public init(street: String, suite: String, city: String, zipcode: String, geo: Geo) {
|
||||||
|
self.street = street
|
||||||
|
self.suite = suite
|
||||||
|
self.city = city
|
||||||
|
self.zipcode = zipcode
|
||||||
|
self.geo = geo
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Geo: Decodable {
|
||||||
|
public var lat: String
|
||||||
|
public var lng: String
|
||||||
|
|
||||||
|
public init(lat: String, lng: String) {
|
||||||
|
self.lat = lat
|
||||||
|
self.lng = lng
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Company: Decodable {
|
public struct Company: Decodable {
|
||||||
var name: String
|
public var name: String
|
||||||
var catchPhrase: String
|
public var catchPhrase: String
|
||||||
var bs: String
|
public var bs: String
|
||||||
|
|
||||||
|
public init(name: String, catchPhrase: String, bs: String) {
|
||||||
|
self.name = name
|
||||||
|
self.catchPhrase = catchPhrase
|
||||||
|
self.bs = bs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ struct UserInfo: View {
|
|||||||
@State private var darkModeEnabled = false
|
@State private var darkModeEnabled = false
|
||||||
@State private var showLogoutAlert = false
|
@State private var showLogoutAlert = false
|
||||||
@State private var avatarImage: UIImage? // Add this line
|
@State private var avatarImage: UIImage? // Add this line
|
||||||
|
@State private var name: String = ""
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
@ -39,53 +40,49 @@ struct UserInfo: View {
|
|||||||
.font(Typography.font(for: .title))
|
.font(Typography.font(for: .title))
|
||||||
.frame(maxWidth: .infinity, alignment: .center)
|
.frame(maxWidth: .infinity, alignment: .center)
|
||||||
|
|
||||||
// Avatar
|
// Avatar section
|
||||||
ZStack {
|
VStack {
|
||||||
// Show either the SVG or the uploaded image
|
Text("your name")
|
||||||
if let avatarImage = avatarImage {
|
.font(.headline)
|
||||||
Image(uiImage: avatarImage)
|
.padding(.bottom, 10)
|
||||||
.resizable()
|
|
||||||
.scaledToFill()
|
|
||||||
.frame(width: 200, height: 200)
|
|
||||||
.clipShape(Circle())
|
|
||||||
} else {
|
|
||||||
SVGImage(svgName: "Avatar")
|
|
||||||
.frame(width: 200, height: 200)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the AvatarUploader is on top and covers the entire area
|
// Avatar image or placeholder
|
||||||
AvatarUploader(selectedImage: $avatarImage, size: 200)
|
Circle()
|
||||||
.contentShape(Rectangle()) // This makes the entire area tappable
|
.fill(Color.gray.opacity(0.3))
|
||||||
|
.frame(width: 100, height: 100)
|
||||||
|
.overlay(
|
||||||
|
Image(systemName: "person.fill")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.padding(30)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.frame(width: 200, height: 200)
|
.padding(.top, 30)
|
||||||
.padding(.vertical, 20)
|
|
||||||
|
|
||||||
// Buttons
|
// Name input field
|
||||||
|
TextField("Enter your name", text: $name)
|
||||||
|
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||||
|
.padding(.horizontal, 40)
|
||||||
|
.padding(.top, 20)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
// Next/Open button
|
||||||
Button(action: {
|
Button(action: {
|
||||||
// Action for first button
|
// Action for open button
|
||||||
}) {
|
}) {
|
||||||
Text("Upload from Gallery")
|
Text("Open")
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.padding()
|
.padding()
|
||||||
.foregroundColor(.black)
|
.foregroundColor(.black)
|
||||||
.background(
|
.background(
|
||||||
RoundedRectangle(cornerRadius: 25)
|
RoundedRectangle(cornerRadius: 25)
|
||||||
.fill(Color(red: 1.0, green: 0.973, blue: 0.871))
|
.fill(Color(red: 1.0, green: 0.714, blue: 0.271))
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Button(action: {
|
|
||||||
// Action for second button
|
|
||||||
}) {
|
|
||||||
Text("Take a Photo")
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.padding()
|
|
||||||
.foregroundColor(.black)
|
|
||||||
.background(
|
|
||||||
RoundedRectangle(cornerRadius: 25)
|
|
||||||
.fill(Color(red: 1.0, green: 0.973, blue: 0.871))
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
.padding(.bottom, 30)
|
||||||
|
.padding(.horizontal, 20)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.background(Color(.white))
|
.background(Color(.white))
|
||||||
|
|||||||
92
wake/View/Welcome/SplashView.swift
Normal file
92
wake/View/Welcome/SplashView.swift
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SplashView: View {
|
||||||
|
@State private var isAnimating = false
|
||||||
|
@State private var showLogin = false
|
||||||
|
@EnvironmentObject private var authState: AuthState
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationView {
|
||||||
|
ZStack {
|
||||||
|
// 背景渐变
|
||||||
|
LinearGradient(
|
||||||
|
gradient: Gradient(colors: [
|
||||||
|
Theme.Colors.primary, // Primary color with some transparency
|
||||||
|
Theme.Colors.primaryDark, // Darker shade of the primary color
|
||||||
|
]),
|
||||||
|
startPoint: .topLeading,
|
||||||
|
endPoint: .bottomTrailing
|
||||||
|
)
|
||||||
|
.edgesIgnoringSafeArea(.all)
|
||||||
|
VStack(spacing: 50) {
|
||||||
|
Spacer()
|
||||||
|
// 欢迎文字动画
|
||||||
|
Text("Welcome")
|
||||||
|
.font(.system(size: 40, weight: .bold, design: .rounded))
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
.scaleEffect(isAnimating ? 1.1 : 0.9)
|
||||||
|
.opacity(isAnimating ? 1 : 0.3)
|
||||||
|
.animation(
|
||||||
|
.easeInOut(duration: 1.5)
|
||||||
|
.repeatForever(autoreverses: true),
|
||||||
|
value: isAnimating
|
||||||
|
)
|
||||||
|
|
||||||
|
// 动画图标
|
||||||
|
Image(systemName: "moon.stars.fill")
|
||||||
|
.font(.system(size: 120))
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
.rotationEffect(.degrees(isAnimating ? 360 : 0))
|
||||||
|
.scaleEffect(isAnimating ? 1.2 : 0.8)
|
||||||
|
.animation(
|
||||||
|
.easeInOut(duration: 2)
|
||||||
|
.repeatForever(autoreverses: true),
|
||||||
|
value: isAnimating
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
// 圆形按钮
|
||||||
|
Button(action: {
|
||||||
|
withAnimation {
|
||||||
|
showLogin = true
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Image(systemName: "arrow.right")
|
||||||
|
.font(.title)
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.frame(width: 140, height: 140)
|
||||||
|
.background(
|
||||||
|
Circle()
|
||||||
|
.fill(Color.accentColor.opacity(0.7)) // 80% opacity
|
||||||
|
.shadow(radius: 10)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.padding(.bottom, 40)
|
||||||
|
.background(
|
||||||
|
NavigationLink(destination: LoginView().environmentObject(authState), isActive: $showLogin) {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
.hidden()
|
||||||
|
)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
isAnimating = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationViewStyle(StackNavigationViewStyle())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预览
|
||||||
|
#if DEBUG
|
||||||
|
struct SplashView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
SplashView()
|
||||||
|
.environmentObject(AuthState.shared)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@ -1,20 +1,12 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UIKit
|
import UIKit
|
||||||
import SwiftData
|
import SwiftData
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct WakeApp: App {
|
struct WakeApp: App {
|
||||||
// init() {
|
@StateObject private var authState = AuthState.shared
|
||||||
// // 打印所有可用的字体
|
@State private var showSplash = true
|
||||||
// print("\n=== 所有可用的字体 ===")
|
|
||||||
// for family in UIFont.familyNames.sorted() {
|
|
||||||
// print("\n\(family):")
|
|
||||||
// for name in UIFont.fontNames(forFamilyName: family).sorted() {
|
|
||||||
// print(" - \(name)")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// 使用更简单的方式创建模型容器
|
// 使用更简单的方式创建模型容器
|
||||||
let container: ModelContainer
|
let container: ModelContainer
|
||||||
|
|
||||||
@ -32,26 +24,77 @@ struct WakeApp: App {
|
|||||||
// 3. 重新创建容器
|
// 3. 重新创建容器
|
||||||
container = try! ModelContainer(for: Login.self)
|
container = try! ModelContainer(for: Login.self)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
// 配置网络层
|
||||||
|
configureNetwork()
|
||||||
|
}
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
ContentView()
|
ZStack {
|
||||||
// SettingsView()
|
if showSplash {
|
||||||
// 导航栏按钮
|
// 显示启动页
|
||||||
// TabView{
|
SplashView()
|
||||||
// ContentView()
|
.environmentObject(authState)
|
||||||
// .tabItem{
|
.onAppear {
|
||||||
// Label("wake", systemImage: "book")
|
// 启动页显示时检查token有效性
|
||||||
// }
|
checkTokenValidity()
|
||||||
// SettingView()
|
}
|
||||||
// .tabItem{
|
} else {
|
||||||
// Label("setting", systemImage: "gear")
|
// 根据登录状态显示不同视图
|
||||||
// }
|
if authState.isAuthenticated {
|
||||||
// }
|
// 已登录:显示主界面
|
||||||
|
ContentView()
|
||||||
|
.environmentObject(authState)
|
||||||
|
} else {
|
||||||
|
// 未登录:显示登录界面
|
||||||
|
ContentView()
|
||||||
|
.environmentObject(authState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
// 3秒后自动隐藏启动页
|
||||||
|
// DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
||||||
|
// withAnimation {
|
||||||
|
// showSplash = false
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 注入模型容器到环境中
|
|
||||||
.modelContainer(container)
|
.modelContainer(container)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - 私有方法
|
||||||
|
|
||||||
|
/// 配置网络层
|
||||||
|
private func configureNetwork() {
|
||||||
|
// 配置网络请求超时时间等
|
||||||
|
let configuration = URLSessionConfiguration.default
|
||||||
|
configuration.timeoutIntervalForRequest = 30
|
||||||
|
configuration.timeoutIntervalForResource = 60
|
||||||
|
|
||||||
|
// 可以在这里添加其他网络配置
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 检查token有效性
|
||||||
|
private func checkTokenValidity() {
|
||||||
|
guard TokenManager.shared.hasToken,
|
||||||
|
let token = KeychainHelper.getAccessToken() else {
|
||||||
|
showSplash = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查token是否有效
|
||||||
|
if TokenManager.shared.isTokenValid(token) {
|
||||||
|
authState.isAuthenticated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3秒后自动隐藏启动页
|
||||||
|
// DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
||||||
|
// withAnimation {
|
||||||
|
// showSplash = false
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user