import SwiftUI import AuthenticationServices import Alamofire import CryptoKit import Foundation /// 主登录视图 - 处理苹果登录 struct LoginView: View { // MARK: - Properties @State private var isLoading = false @State private var showError = false @State private var errorMessage = "" @State private var currentNonce: String? @State private var isLoggedIn = false // MARK: - Body var body: some View { ZStack { // Background Color(red: 1.0, green: 0.67, blue: 0.15) .ignoresSafeArea() VStack(alignment: .leading, spacing: 4) { Text("Hi, I'm MeMo!") .font(Typography.font(for: .largeTitle, family: .quicksandBold)) .foregroundColor(.black) .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 24) .padding(.top, 44) Text("Welcome~") .font(Typography.font(for: .largeTitle, family: .quicksandBold)) .foregroundColor(.black) .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 24) .padding(.bottom, 20) Spacer() } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) VStack(spacing: 16) { Spacer() signInButton() .padding(.horizontal, 24) termsAndPrivacyView() } .frame(maxWidth: .infinity) .alert(isPresented: $showError) { Alert( title: Text("Error"), message: Text(errorMessage), dismissButton: .default(Text("OK")) ) } if isLoading { loadingView() } } .navigationBarBackButtonHidden(true) .navigationBarHidden(true) } // MARK: - Views private func signInButton() -> some View { print(" [1] 用户点击了登录按钮") return AppleSignInButton { request in print(" 开始创建登录请求") let nonce = String.randomURLSafeString(length: 32) self.currentNonce = nonce request.nonce = self.sha256(nonce) request.requestedScopes = [.fullName, .email] print(" 登录请求配置完成,nonce 已设置") } onCompletion: { result in print(" 收到 Apple 登录回调") switch result { case .success(let authResults): print(" Apple 登录授权成功,开始处理凭证") if let appleIDCredential = authResults.credential as? ASAuthorizationAppleIDCredential { print(" 成功获取 Apple ID 凭证") self.processAppleIDCredential(appleIDCredential) } else { print(" 凭证类型转换失败") print(" 凭证类型: \(type(of: authResults.credential))") self.showError(message: "无法处理登录凭证") } case .failure(let error): print(" Apple 登录失败: \(error.localizedDescription)") print(" 错误详情: \(error as NSError)") self.handleSignInError(error) } } } private func termsAndPrivacyView() -> some View { VStack(spacing: 4) { HStack { Text("By continuing, you agree to our") .font(.caption) .foregroundColor(.themeTextMessage) Button("Terms of") { openURL("https://yourwebsite.com/terms") } .font(.caption2) .foregroundColor(.themeTextMessageMain) } .multilineTextAlignment(.center) .padding(.horizontal, 24) HStack(spacing: 8) { Button("Service") { if let url = URL(string: "https://memorywake.com/privacy-policy") { UIApplication.shared.open(url) } } .font(.caption2) .foregroundColor(.themeTextMessageMain) Text("and") .foregroundColor(.themeTextMessage) .font(.caption) Button("Privacy Policy") { if let url = URL(string: "https://memorywake.com/privacy-policy") { UIApplication.shared.open(url) } } .font(.caption2) .foregroundColor(.themeTextMessageMain) } .padding(.top, 4) } .fixedSize(horizontal: false, vertical: true) .frame(maxWidth: .infinity) .padding(.horizontal, 24) .padding(.vertical, 12) } private func loadingView() -> some View { ZStack { Color.black.opacity(0.4) .edgesIgnoringSafeArea(.all) ProgressView() .scaleEffect(1.5) .progressViewStyle(CircularProgressViewStyle(tint: .white)) } } // MARK: - Authentication private func handleAppleSignIn(result: Result) { print(" 开始处理登录结果...") switch result { case .success(let authResults): print(" 登录授权成功") processAppleIDCredential(authResults.credential) case .failure(let error): print(" 登录失败: \(error.localizedDescription)") handleSignInError(error) } } private func processAppleIDCredential(_ credential: ASAuthorizationCredential) { print(" 开始处理 Apple ID 凭证") guard let appleIDCredential = credential as? ASAuthorizationAppleIDCredential else { print(" 凭证类型不匹配") print(" 实际凭证类型: \(type(of: credential))") showError(message: "无法处理Apple ID凭证") return } let userId = appleIDCredential.user let email = appleIDCredential.email ?? "" let fullName = [ appleIDCredential.fullName?.givenName, appleIDCredential.fullName?.familyName ] .compactMap { $0 } .joined(separator: " ") print(" 用户数据 - ID: \(userId), 邮箱: \(email.isEmpty ? "未提供" : email), 姓名: \(fullName.isEmpty ? "未提供" : fullName)") guard let identityTokenData = appleIDCredential.identityToken, let identityToken = String(data: identityTokenData, encoding: .utf8) else { print(" 无法获取身份令牌") showError(message: "无法获取身份令牌") return } print(" 成功获取 identityToken") var authCode: String? = nil if let authCodeData = appleIDCredential.authorizationCode { authCode = String(data: authCodeData, encoding: .utf8) print(" 获取到授权码") } else { print(" 未获取到授权码(可选)") } print(" 准备调用后端认证接口...") authenticateWithBackend( identityToken: identityToken, authCode: authCode ) } // MARK: - Network private func authenticateWithBackend( identityToken: String, authCode: String? ) { print(" 开始后端认证流程...") isLoading = true var parameters: [String: Any] = [ "token": identityToken, "provider": "Apple", ] if let authCode = authCode { parameters["authorization_code"] = authCode print(" 添加授权码到请求参数") } print(" 发送认证请求到服务器...") print(" 接口: /iam/login/oauth") print(" 请求参数: \(parameters.keys)") NetworkService.shared.post( path: "/iam/login/oauth", parameters: parameters ) { (result: Result) -> Void in let handleResult: () -> Void = { self.isLoading = false switch result { case .success(let authResponse): print("✅ [15] 后端认证成功") if let loginInfo = authResponse.data?.userLoginInfo { print("🔑 [16] 保存认证信息") print(" - 用户ID: \(loginInfo.userId)") print(" - 昵称: \(loginInfo.nickname)") KeychainHelper.saveAccessToken(loginInfo.accessToken) KeychainHelper.saveRefreshToken(loginInfo.refreshToken) print("🔄 [17] 准备跳转到用户信息页面...") print("🔍 isLoggedIn 当前值: \(self.isLoggedIn)") self.isLoggedIn = true print("✅ [18] isLoggedIn 已设置为 true") print("🎉 登录流程完成,即将跳转") } else { print("⚠️ [16] 认证成功但返回的用户信息不完整") self.errorMessage = "登录信息不完整,请重试" self.showError = true } // 跳转到userinfo Router.shared.navigate(to: .userInfo) case .failure(let error): print("❌ [15] 后端认证失败") print("⚠️ 错误类型: \(type(of: error))") print("📝 错误信息: \(error.localizedDescription)") var errorMessage = "登录失败,请重试" switch error { case .invalidURL: print(" → 无效的URL") errorMessage = "服务器地址无效" case .noData: print(" → 服务器未返回数据") errorMessage = "服务器未响应,请检查网络" case .decodingError(let error): print(" → 数据解析失败: \(error.localizedDescription)") errorMessage = "服务器响应格式错误" case .serverError(let message): print(" → 服务器错误: \(message)") errorMessage = "服务器错误: \(message)" case .unauthorized: print(" → 认证失败: 未授权") errorMessage = "登录信息已过期,请重新登录" case .networkError(let error): print(" → 网络错误: \(error.localizedDescription)") errorMessage = "网络连接失败,请检查网络" case .other(let error): print(" → 其他错误: \(error.localizedDescription)") errorMessage = "发生未知错误" case .unknownError(let error): print(" → 未知错误: \(error.localizedDescription)") errorMessage = "发生未知错误" case .invalidParameters: print(" → 无效的参数") errorMessage = "请求参数错误,请重试" } self.errorMessage = errorMessage self.showError = true print("❌ 登录失败: \(errorMessage)") } } // Execute on main thread if Thread.isMainThread { handleResult() } else { DispatchQueue.main.async(execute: handleResult) } } } // MARK: - Helpers private func handleSuccessfulAuthentication() { print(" 登录成功,准备跳转到用户信息页面...") print(" isLoggedIn before update: \(isLoggedIn)") DispatchQueue.main.async { print(" Setting isLoggedIn to true") self.isLoggedIn = true print(" isLoggedIn after update: \(self.isLoggedIn)") } } private func handleSignInError(_ error: Error) { let errorMessage = (error as NSError).localizedDescription print(" 登录错误: \(errorMessage)") showError(message: "登录失败: \(error.localizedDescription)") } private func handleAuthenticationError(_ error: Error) { let errorMessage = error.localizedDescription print(" 认证错误: \(errorMessage)") DispatchQueue.main.async { self.isLoggedIn = false self.showError(message: "登录失败: \(errorMessage)") } } private func showError(message: String) { DispatchQueue.main.async { self.errorMessage = message self.showError = true } } private func openURL(_ string: String) { guard let url = URL(string: string) else { return } UIApplication.shared.open(url) } private func sha256(_ input: String) -> String { let inputData = Data(input.utf8) let hashedData = SHA256.hash(data: inputData) return hashedData.compactMap { String(format: "%02x", $0) }.joined() } } // MARK: - Extensions extension String { static func randomURLSafeString(length: Int) -> String { let characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" var randomString = "" for _ in 0..