diff --git a/ios/OralTrainer.xcodeproj/project.xcworkspace/xcuserdata/liushuming.xcuserdatad/UserInterfaceState.xcuserstate b/ios/OralTrainer.xcodeproj/project.xcworkspace/xcuserdata/liushuming.xcuserdatad/UserInterfaceState.xcuserstate index af3cff7..0cbb5fc 100644 Binary files a/ios/OralTrainer.xcodeproj/project.xcworkspace/xcuserdata/liushuming.xcuserdatad/UserInterfaceState.xcuserstate and b/ios/OralTrainer.xcodeproj/project.xcworkspace/xcuserdata/liushuming.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/ios/OralTrainer/Config.swift b/ios/OralTrainer/Config.swift index 48e4c07..ee12455 100644 --- a/ios/OralTrainer/Config.swift +++ b/ios/OralTrainer/Config.swift @@ -17,4 +17,7 @@ enum AppConfig { static let assessmentTimeout: TimeInterval = 180 static let sha256Pattern = #"^[0-9a-fA-F]{64}$"# + + /// 当前登录用户的认证 token,由 AuthManager 在登录/注销时设置。 + static var authToken: String = "" } diff --git a/ios/OralTrainer/Services/HTTPClient.swift b/ios/OralTrainer/Services/HTTPClient.swift index 9801fbb..38fff5d 100644 --- a/ios/OralTrainer/Services/HTTPClient.swift +++ b/ios/OralTrainer/Services/HTTPClient.swift @@ -25,14 +25,17 @@ enum HTTPClient { _ type: T.Type, baseURL: String, path: String, - timeout: TimeInterval + timeout: TimeInterval, + additionalHeaders: [String: String] = [:] ) async throws -> (value: T, effectiveBaseURL: String) { + var headers = ["Accept": "application/json"] + headers.merge(additionalHeaders) { _, new in new } let result = try await execute( baseURL: baseURL, path: path, method: "GET", timeout: timeout, - headers: ["Accept": "application/json"] + headers: headers ) guard (200...299).contains(result.statusCode) else { let body = String(data: result.body, encoding: .utf8) ?? "" diff --git a/ios/OralTrainer/Services/SentenceBoundaryAPI.swift b/ios/OralTrainer/Services/SentenceBoundaryAPI.swift index e3bba32..656f923 100644 --- a/ios/OralTrainer/Services/SentenceBoundaryAPI.swift +++ b/ios/OralTrainer/Services/SentenceBoundaryAPI.swift @@ -20,11 +20,16 @@ enum SentenceBoundaryAPI { guard hash.range(of: AppConfig.sha256Pattern, options: .regularExpression) != nil else { throw SentenceBoundaryAPIError.invalidHash } + var headers: [String: String] = [:] + if !AppConfig.authToken.isEmpty { + headers["Authorization"] = "Bearer \(AppConfig.authToken)" + } let result = try await HTTPClient.getJSON( SentenceBoundaryDocument.self, baseURL: AppConfig.serverBaseURL, path: "api/v1/videos/\(hash)/sentence-boundaries", - timeout: AppConfig.readTimeout + timeout: AppConfig.readTimeout, + additionalHeaders: headers ) guard result.value.videoHash.lowercased() == hash else { throw SentenceBoundaryAPIError.hashMismatch diff --git a/ios/OralTrainer/Views/AuthView.swift b/ios/OralTrainer/Views/AuthView.swift index 3e6961e..3860c3b 100644 --- a/ios/OralTrainer/Views/AuthView.swift +++ b/ios/OralTrainer/Views/AuthView.swift @@ -15,6 +15,7 @@ final class AuthManager: ObservableObject { let defaults = UserDefaults.standard token = defaults.string(forKey: "oral_trainer_token") ?? "" nickname = defaults.string(forKey: "oral_trainer_nickname") ?? "" + AppConfig.authToken = token } var isLoggedIn: Bool { !token.isEmpty } @@ -22,6 +23,7 @@ final class AuthManager: ObservableObject { func saveSession(_ session: AuthSession) { token = session.token nickname = session.user.nickname + AppConfig.authToken = session.token let defaults = UserDefaults.standard defaults.set(session.token, forKey: "oral_trainer_token") defaults.set(session.user.nickname, forKey: "oral_trainer_nickname") @@ -30,6 +32,7 @@ final class AuthManager: ObservableObject { func logout() { token = "" nickname = "" + AppConfig.authToken = "" showingRegister = false showingSSO = false showingPhoneBinding = false