feat: 登录接口联调
This commit is contained in:
parent
7aea7b1535
commit
ef65019e46
Binary file not shown.
@ -3,11 +3,19 @@ import Foundation
|
|||||||
/// API 配置信息
|
/// API 配置信息
|
||||||
public enum APIConfig {
|
public enum APIConfig {
|
||||||
/// API 基础 URL
|
/// API 基础 URL
|
||||||
public static let baseURL = "https://api.memorywake.com/api/v1"
|
public static let baseURL = "https://api-dev.memorywake.com:31274/api/v1"
|
||||||
|
|
||||||
/// 认证 token - 生产环境中应该存储在 Keychain 中
|
|
||||||
public static let authToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJqdGkiOjczNjM0ODY2MTE1MDc2NDY0NjQsImlkZW50aXR5IjoiNzM1MDQzOTY2MzExNjYxOTg4OCIsImV4cCI6MTc1NjE5NjgxNX0.hRC_So6LHuR6Gx-bDyO8aliVOd-Xumul8M7cydi2pTxHPweBx4421AfZ5BjGoEEwRZPIXJ5z7a1aDB7qvjpLCA"
|
|
||||||
|
|
||||||
|
/// 认证 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] {
|
public static var authHeaders: [String: String] {
|
||||||
return [
|
return [
|
||||||
@ -16,4 +24,4 @@ public enum APIConfig {
|
|||||||
"Accept": "application/json"
|
"Accept": "application/json"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
83
wake/Utils/KeychainHelper.swift
Normal file
83
wake/Utils/KeychainHelper.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,7 +12,6 @@ struct LoginView: View {
|
|||||||
@State private var errorMessage = ""
|
@State private var errorMessage = ""
|
||||||
@State private var currentNonce: String?
|
@State private var currentNonce: String?
|
||||||
@State private var isLoggedIn = false
|
@State private var isLoggedIn = false
|
||||||
|
|
||||||
// MARK: - Body
|
// MARK: - Body
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@ -196,9 +195,12 @@ struct LoginView: View {
|
|||||||
|
|
||||||
print("🔵 [Apple ID] 准备调用后端认证...")
|
print("🔵 [Apple ID] 准备调用后端认证...")
|
||||||
authenticateWithBackend(
|
authenticateWithBackend(
|
||||||
userId: userId,
|
userId: appleIDCredential.user,
|
||||||
email: email,
|
email: appleIDCredential.email ?? "",
|
||||||
name: fullName,
|
name: [appleIDCredential.fullName?.givenName,
|
||||||
|
appleIDCredential.fullName?.familyName]
|
||||||
|
.compactMap { $0 }
|
||||||
|
.joined(separator: " "),
|
||||||
identityToken: identityToken,
|
identityToken: identityToken,
|
||||||
authCode: authCode
|
authCode: authCode
|
||||||
)
|
)
|
||||||
@ -216,37 +218,160 @@ struct LoginView: View {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
print("🔵 [Backend] 开始后端认证...")
|
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] = [
|
var parameters: [String: Any] = [
|
||||||
"appleUserId": userId,
|
"provider": "Apple",
|
||||||
|
"token": identityToken,
|
||||||
|
"userId": userId,
|
||||||
"email": email,
|
"email": email,
|
||||||
"name": name,
|
"name": name,
|
||||||
"identityToken": identityToken
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if let authCode = authCode {
|
if let authCode = authCode {
|
||||||
parameters["authorizationCode"] = authCode
|
parameters["authorization_code"] = authCode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
print("📤 [Backend] 请求URL: \(endpoint)")
|
||||||
print("📤 [Backend] 请求参数: \(parameters)")
|
print("📤 [Backend] 请求参数: \(parameters)")
|
||||||
|
|
||||||
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default)
|
var request = URLRequest(url: url)
|
||||||
.validate()
|
request.httpMethod = "POST"
|
||||||
.responseJSON { response in
|
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
|
self.isLoading = false
|
||||||
|
|
||||||
switch response.result {
|
// 1. 处理网络错误
|
||||||
case .success(let value):
|
if let error = error {
|
||||||
print("✅ [Backend] 认证成功: \(value)")
|
print("❌ [Backend] 请求失败: \(error.localizedDescription)")
|
||||||
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)")
|
|
||||||
}
|
|
||||||
self.handleAuthenticationError(error)
|
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
|
// MARK: - Helpers
|
||||||
@ -264,7 +389,7 @@ struct LoginView: View {
|
|||||||
showError(message: "登录失败: \(error.localizedDescription)")
|
showError(message: "登录失败: \(error.localizedDescription)")
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleAuthenticationError(_ error: AFError) {
|
private func handleAuthenticationError(_ error: Error) {
|
||||||
let errorMessage = error.localizedDescription
|
let errorMessage = error.localizedDescription
|
||||||
print("❌ [Auth] 认证错误: \(errorMessage)")
|
print("❌ [Auth] 认证错误: \(errorMessage)")
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user