feat: 登录接口联调

This commit is contained in:
jinyaqiu 2025-08-19 20:34:25 +08:00
parent 5edee649bd
commit 18147895a2
4 changed files with 242 additions and 26 deletions

View File

@ -3,11 +3,19 @@ import Foundation
/// API
public enum APIConfig {
/// API URL
public static let baseURL = "https://api.memorywake.com/api/v1"
/// token - Keychain
public static let authToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJqdGkiOjczNjM0ODY2MTE1MDc2NDY0NjQsImlkZW50aXR5IjoiNzM1MDQzOTY2MzExNjYxOTg4OCIsImV4cCI6MTc1NjE5NjgxNX0.hRC_So6LHuR6Gx-bDyO8aliVOd-Xumul8M7cydi2pTxHPweBx4421AfZ5BjGoEEwRZPIXJ5z7a1aDB7qvjpLCA"
public static let baseURL = "https://api-dev.memorywake.com:31274/api/v1"
/// token - Keychain
public static var authToken: String {
let token = KeychainHelper.getAccessToken() ?? ""
if !token.isEmpty {
print("🔑 [APIConfig] 当前访问令牌: \(token.prefix(10))...") // 10
} else {
print("⚠️ [APIConfig] 未找到访问令牌")
}
return token
}
///
public static var authHeaders: [String: String] {
return [
@ -16,4 +24,4 @@ public enum APIConfig {
"Accept": "application/json"
]
}
}
}

View File

@ -0,0 +1,83 @@
import Foundation
import Security
/// Keychain
public class KeychainHelper {
// Keychain
private enum KeychainKey: String {
case accessToken = "com.memorywake.accessToken"
case refreshToken = "com.memorywake.refreshToken"
}
/// 访 Keychain
public static func saveAccessToken(_ token: String) -> Bool {
return save(token, for: .accessToken)
}
/// Keychain 访
public static func getAccessToken() -> String? {
return get(for: .accessToken)
}
/// Keychain
public static func saveRefreshToken(_ token: String) -> Bool {
return save(token, for: .refreshToken)
}
/// Keychain
public static func getRefreshToken() -> String? {
return get(for: .refreshToken)
}
///
public static func clearTokens() {
delete(for: .accessToken)
delete(for: .refreshToken)
}
// MARK: -
private static func save(_ string: String, for key: KeychainKey) -> Bool {
guard let data = string.data(using: .utf8) else { return false }
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key.rawValue,
kSecValueData as String: data
]
//
SecItemDelete(query as CFDictionary)
//
let status = SecItemAdd(query as CFDictionary, nil)
return status == errSecSuccess
}
private static func get(for key: KeychainKey) -> String? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key.rawValue,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne
]
var dataTypeRef: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
guard status == errSecSuccess, let data = dataTypeRef as? Data else {
return nil
}
return String(data: data, encoding: .utf8)
}
private static func delete(for key: KeychainKey) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key.rawValue
]
SecItemDelete(query as CFDictionary)
}
}

View File

@ -12,7 +12,6 @@ struct LoginView: View {
@State private var errorMessage = ""
@State private var currentNonce: String?
@State private var isLoggedIn = false
// MARK: - Body
var body: some View {
@ -196,9 +195,12 @@ struct LoginView: View {
print("🔵 [Apple ID] 准备调用后端认证...")
authenticateWithBackend(
userId: userId,
email: email,
name: fullName,
userId: appleIDCredential.user,
email: appleIDCredential.email ?? "",
name: [appleIDCredential.fullName?.givenName,
appleIDCredential.fullName?.familyName]
.compactMap { $0 }
.joined(separator: " "),
identityToken: identityToken,
authCode: authCode
)
@ -216,37 +218,160 @@ struct LoginView: View {
isLoading = true
print("🔵 [Backend] 开始后端认证...")
let url = "https://your-api-endpoint.com/api/auth/apple"
let endpoint = "\(APIConfig.baseURL)/iam/login/oauth"
guard let url = URL(string: endpoint) else {
print("❌ [Backend] 无效的URL: \(endpoint)")
self.handleAuthenticationError(NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "无效的URL"]))
return
}
var parameters: [String: Any] = [
"appleUserId": userId,
"provider": "Apple",
"token": identityToken,
"userId": userId,
"email": email,
"name": name,
"identityToken": identityToken
]
if let authCode = authCode {
parameters["authorizationCode"] = authCode
parameters["authorization_code"] = authCode
}
print("📤 [Backend] 请求URL: \(endpoint)")
print("📤 [Backend] 请求参数: \(parameters)")
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default)
.validate()
.responseJSON { response in
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
do {
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: [])
} catch {
print("❌ [Backend] 参数序列化失败: \(error.localizedDescription)")
self.handleAuthenticationError(error)
return
}
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
self.isLoading = false
switch response.result {
case .success(let value):
print("✅ [Backend] 认证成功: \(value)")
self.handleSuccessfulAuthentication()
case .failure(let error):
print("❌ [Backend] 认证失败: \(error.localizedDescription)")
if let data = response.data, let json = String(data: data, encoding: .utf8) {
print("❌ [Backend] 错误详情: \(json)")
}
// 1.
if let error = error {
print("❌ [Backend] 请求失败: \(error.localizedDescription)")
self.handleAuthenticationError(error)
return
}
// 2.
guard let httpResponse = response as? HTTPURLResponse else {
let error = NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "无效的服务器响应"])
print("❌ [Backend] \(error.localizedDescription)")
self.handleAuthenticationError(error)
return
}
// 3.
let statusCode = httpResponse.statusCode
print("""
📥 [Backend] :
- : \(statusCode)
- URL: \(httpResponse.url?.absoluteString ?? "N/A")
- Headers: \(httpResponse.allHeaderFields)
""")
// 4.
if let data = data {
if let jsonString = String(data: data, encoding: .utf8) {
print("📦 [Backend] 响应内容: \(jsonString)")
}
// 5.
do {
// JSON
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
print("✅ [Backend] 响应解析成功")
print("📦 [Backend] 响应内容: \(json)")
//
if let code = json["code"] as? Int, code != 0 {
let errorMsg = json["message"] as? String ?? "未知错误"
print("❌ [Backend] 请求失败: \(errorMsg)")
self.showError(message: errorMsg)
return
}
// data
guard let responseData = json["data"] as? [String: Any] else {
print("⚠️ [Backend] 未找到 data 字段")
self.showError(message: "服务器返回数据格式错误")
return
}
// user_login_info
if let userLoginInfo = responseData["user_login_info"] as? [String: Any] {
print("👤 [Backend] 用户登录信息: \(userLoginInfo)")
// Keychain
if let accessToken = userLoginInfo["access_token"] as? String {
_ = KeychainHelper.saveAccessToken(accessToken)
print("🔑 [Keychain] 访问令牌已保存")
// UserDefaults
var userInfo: [String: Any] = [
"user_id": userLoginInfo["user_id"] as? Int64 ?? 0,
"account": userLoginInfo["account"] as? String ?? "",
"nickname": userLoginInfo["nickname"] as? String ?? "",
"avatar": userLoginInfo["avatar_file_url"] as? String ?? ""
]
UserDefaults.standard.set(userInfo, forKey: "currentUserInfo")
print("👤 [UserDefaults] 用户信息已保存")
}
if let refreshToken = userLoginInfo["refresh_token"] as? String {
_ = KeychainHelper.saveRefreshToken(refreshToken)
print("🔄 [Keychain] 刷新令牌已保存")
}
//
DispatchQueue.main.async {
self.handleSuccessfulAuthentication()
}
return
} else {
print("⚠️ [Backend] 未找到 user_login_info 字段")
self.showError(message: "登录信息不完整")
}
}
} catch {
print("⚠️ [Backend] 响应解析失败: \(error.localizedDescription)")
self.showError(message: "数据解析失败")
}
}
// 6. return
if statusCode < 200 || statusCode >= 300 {
let errorMessage: String
switch statusCode {
case 400:
errorMessage = "请求参数错误"
case 401:
errorMessage = "认证失败,请重新登录"
case 403:
errorMessage = "权限不足"
case 404:
errorMessage = "请求的接口不存在"
case 500...599:
errorMessage = "服务器内部错误,请稍后重试"
default:
errorMessage = "未知错误 (状态码: \(statusCode))"
}
self.showError(message: errorMessage)
}
}
}
task.resume()
}
// MARK: - Helpers
@ -264,7 +389,7 @@ struct LoginView: View {
showError(message: "登录失败: \(error.localizedDescription)")
}
private func handleAuthenticationError(_ error: AFError) {
private func handleAuthenticationError(_ error: Error) {
let errorMessage = error.localizedDescription
print("❌ [Auth] 认证错误: \(errorMessage)")
DispatchQueue.main.async {