feat: 登录
This commit is contained in:
parent
fdd2715ec8
commit
6a05bd0dc2
43
wake/Models/AuthModels.swift
Normal file
43
wake/Models/AuthModels.swift
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// API基础响应模型
|
||||||
|
struct BaseResponse<T: Codable>: Codable {
|
||||||
|
let code: Int
|
||||||
|
let data: T?
|
||||||
|
let message: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 用户登录信息
|
||||||
|
struct UserLoginInfo: Codable {
|
||||||
|
let userId: String
|
||||||
|
let accessToken: String
|
||||||
|
let refreshToken: String
|
||||||
|
let nickname: String
|
||||||
|
let account: String
|
||||||
|
let email: String
|
||||||
|
let avatarFileUrl: String?
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case userId = "user_id"
|
||||||
|
case accessToken = "access_token"
|
||||||
|
case refreshToken = "refresh_token"
|
||||||
|
case nickname
|
||||||
|
case account
|
||||||
|
case email
|
||||||
|
case avatarFileUrl = "avatar_file_url"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 登录响应数据
|
||||||
|
struct LoginResponseData: Codable {
|
||||||
|
let userLoginInfo: UserLoginInfo
|
||||||
|
let isNewUser: Bool
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case userLoginInfo = "user_login_info"
|
||||||
|
case isNewUser = "is_new_user"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 认证响应模型
|
||||||
|
typealias AuthResponse = BaseResponse<LoginResponseData>
|
||||||
@ -1,5 +1,23 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
// 添加登出通知
|
||||||
|
extension Notification.Name {
|
||||||
|
static let userDidLogoutNotification = Notification.Name("UserDidLogoutNotification")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 请求标识符
|
||||||
|
private struct RequestIdentifier {
|
||||||
|
static var currentId: Int = 0
|
||||||
|
static var lock = NSLock()
|
||||||
|
|
||||||
|
static func next() -> Int {
|
||||||
|
lock.lock()
|
||||||
|
defer { lock.unlock() }
|
||||||
|
currentId += 1
|
||||||
|
return currentId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum NetworkError: Error {
|
enum NetworkError: Error {
|
||||||
case invalidURL
|
case invalidURL
|
||||||
case noData
|
case noData
|
||||||
@ -41,6 +59,9 @@ class NetworkService {
|
|||||||
"Accept": "application/json"
|
"Accept": "application/json"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
private var isRefreshing = false
|
||||||
|
private var requestsToRetry: [(URLRequest, (Result<Data, NetworkError>) -> Void, Int)] = []
|
||||||
|
|
||||||
private init() {}
|
private init() {}
|
||||||
|
|
||||||
// MARK: - 基础请求方法
|
// MARK: - 基础请求方法
|
||||||
@ -51,8 +72,13 @@ class NetworkService {
|
|||||||
headers: [String: String]? = nil,
|
headers: [String: String]? = nil,
|
||||||
completion: @escaping (Result<T, NetworkError>) -> Void
|
completion: @escaping (Result<T, NetworkError>) -> Void
|
||||||
) {
|
) {
|
||||||
|
// 生成请求ID
|
||||||
|
let requestId = RequestIdentifier.next()
|
||||||
|
|
||||||
// 构建URL
|
// 构建URL
|
||||||
guard let url = URL(string: APIConfig.baseURL + path) else {
|
let fullURL = APIConfig.baseURL + path
|
||||||
|
guard let url = URL(string: fullURL) else {
|
||||||
|
print("❌ [Network][#\(requestId)][\(method) \(path)] 无效的URL")
|
||||||
completion(.failure(.invalidURL))
|
completion(.failure(.invalidURL))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -81,24 +107,39 @@ class NetworkService {
|
|||||||
do {
|
do {
|
||||||
request.httpBody = try JSONSerialization.data(withJSONObject: parameters)
|
request.httpBody = try JSONSerialization.data(withJSONObject: parameters)
|
||||||
} catch {
|
} catch {
|
||||||
|
print("❌ [Network][#\(requestId)][\(method) \(path)] 参数序列化失败: \(error.localizedDescription)")
|
||||||
completion(.failure(.other(error)))
|
completion(.failure(.other(error)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 打印请求信息(调试用)
|
// 打印请求信息
|
||||||
print("🌐 [Network] \(method) \(url.absoluteString)")
|
print("""
|
||||||
if let headers = request.allHTTPHeaderFields {
|
🌐 [Network][#\(requestId)][\(method) \(path)] 开始请求
|
||||||
print("📤 Headers: \(headers)")
|
🔗 URL: \(url.absoluteString)
|
||||||
}
|
📤 Headers: \(request.allHTTPHeaderFields ?? [:])
|
||||||
if let body = request.httpBody, let bodyString = String(data: body, encoding: .utf8) {
|
📦 Body: \(request.httpBody.flatMap { String(data: $0, encoding: .utf8) } ?? "")
|
||||||
print("📦 Body: \(bodyString)")
|
""")
|
||||||
}
|
|
||||||
|
|
||||||
// 创建任务
|
// 创建任务
|
||||||
|
let startTime = Date()
|
||||||
let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
|
let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
|
||||||
|
let duration = String(format: "%.3fs", Date().timeIntervalSince(startTime))
|
||||||
|
|
||||||
// 处理响应
|
// 处理响应
|
||||||
self?.handleResponse(data: data, response: response, error: error, completion: completion)
|
self?.handleResponse(
|
||||||
|
requestId: requestId,
|
||||||
|
method: method,
|
||||||
|
path: path,
|
||||||
|
data: data,
|
||||||
|
response: response,
|
||||||
|
error: error,
|
||||||
|
request: request,
|
||||||
|
duration: duration,
|
||||||
|
completion: { (result: Result<T, NetworkError>) in
|
||||||
|
completion(result)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开始请求
|
// 开始请求
|
||||||
@ -106,44 +147,119 @@ class NetworkService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func handleResponse<T: Decodable>(
|
private func handleResponse<T: Decodable>(
|
||||||
|
requestId: Int,
|
||||||
|
method: String,
|
||||||
|
path: String,
|
||||||
data: Data?,
|
data: Data?,
|
||||||
response: URLResponse?,
|
response: URLResponse?,
|
||||||
error: Error?,
|
error: Error?,
|
||||||
|
request: URLRequest,
|
||||||
|
duration: String,
|
||||||
completion: @escaping (Result<T, NetworkError>) -> Void
|
completion: @escaping (Result<T, NetworkError>) -> Void
|
||||||
) {
|
) {
|
||||||
// 打印响应信息(调试用)
|
// 打印响应信息
|
||||||
if let httpResponse = response as? HTTPURLResponse {
|
if let httpResponse = response as? HTTPURLResponse {
|
||||||
print("📥 [Network] Status: \(httpResponse.statusCode) \(HTTPURLResponse.localizedString(forStatusCode: httpResponse.statusCode))")
|
let statusCode = httpResponse.statusCode
|
||||||
if let headers = httpResponse.allHeaderFields as? [String: Any] {
|
let statusMessage = HTTPURLResponse.localizedString(forStatusCode: statusCode)
|
||||||
print("📥 Headers: \(headers)")
|
|
||||||
|
// 处理401未授权
|
||||||
|
if statusCode == 401 {
|
||||||
|
print("""
|
||||||
|
🔑 [Network][#\(requestId)][\(method) \(path)] 检测到未授权,尝试刷新token...
|
||||||
|
⏱️ 耗时: \(duration)
|
||||||
|
""")
|
||||||
|
|
||||||
|
// 将请求加入重试队列
|
||||||
|
let dataResult = data.flatMap { Result<Data, NetworkError>.success($0) } ?? .failure(.noData)
|
||||||
|
self.requestsToRetry.append((request, { result in
|
||||||
|
switch result {
|
||||||
|
case .success(let data):
|
||||||
|
do {
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
let result = try decoder.decode(T.self, from: data)
|
||||||
|
print("""
|
||||||
|
✅ [Network][#\(requestId)][\(method) \(path)] 重试成功
|
||||||
|
⏱️ 总耗时: \(duration) (包含token刷新时间)
|
||||||
|
""")
|
||||||
|
completion(.success(result))
|
||||||
|
} catch let decodingError as DecodingError {
|
||||||
|
print("""
|
||||||
|
❌ [Network][#\(requestId)][\(method) \(path)] JSON解析失败
|
||||||
|
🔍 错误: \(decodingError.localizedDescription)
|
||||||
|
📦 原始数据: \(String(data: data, encoding: .utf8) ?? "")
|
||||||
|
""")
|
||||||
|
completion(.failure(.decodingError(decodingError)))
|
||||||
|
} catch {
|
||||||
|
print("""
|
||||||
|
❌ [Network][#\(requestId)][\(method) \(path)] 未知错误
|
||||||
|
🔍 错误: \(error.localizedDescription)
|
||||||
|
""")
|
||||||
|
completion(.failure(.unknownError(error)))
|
||||||
|
}
|
||||||
|
case .failure(let error):
|
||||||
|
print("""
|
||||||
|
❌ [Network][#\(requestId)][\(method) \(path)] 重试失败
|
||||||
|
🔍 错误: \(error.localizedDescription)
|
||||||
|
""")
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
}, requestId))
|
||||||
|
|
||||||
|
// 如果没有正在刷新的请求,则开始刷新token
|
||||||
|
if !isRefreshing {
|
||||||
|
refreshAndRetryRequests()
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查状态码
|
// 处理其他错误状态码
|
||||||
if !(200...299).contains(httpResponse.statusCode) {
|
if !(200...299).contains(statusCode) {
|
||||||
print("❌ [Network] 请求失败,状态码: \(httpResponse.statusCode)")
|
let errorMessage = data.flatMap { String(data: $0, encoding: .utf8) } ?? ""
|
||||||
if let data = data, let errorResponse = String(data: data, encoding: .utf8) {
|
print("""
|
||||||
print("❌ [Network] 错误响应: \(errorResponse)")
|
❌ [Network][#\(requestId)][\(method) \(path)] 请求失败
|
||||||
}
|
📊 状态码: \(statusCode) (\(statusMessage))
|
||||||
|
⏱️ 耗时: \(duration)
|
||||||
|
🔍 错误响应: \(errorMessage)
|
||||||
|
""")
|
||||||
|
completion(.failure(.serverError("状态码: \(statusCode), 响应: \(errorMessage)")))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 成功响应
|
||||||
|
print("""
|
||||||
|
✅ [Network][#\(requestId)][\(method) \(path)] 请求成功
|
||||||
|
📊 状态码: \(statusCode) (\(statusMessage))
|
||||||
|
⏱️ 耗时: \(duration)
|
||||||
|
""")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理网络错误
|
// 处理网络错误
|
||||||
if let error = error {
|
if let error = error {
|
||||||
print("❌ [Network] 网络请求错误: \(error.localizedDescription)")
|
print("""
|
||||||
|
❌ [Network][#\(requestId)][\(method) \(path)] 网络请求失败
|
||||||
|
⏱️ 耗时: \(duration)
|
||||||
|
🔍 错误: \(error.localizedDescription)
|
||||||
|
""")
|
||||||
completion(.failure(.networkError(error)))
|
completion(.failure(.networkError(error)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查数据是否存在
|
// 检查数据是否存在
|
||||||
guard let data = data else {
|
guard let data = data else {
|
||||||
print("❌ [Network] 没有收到数据")
|
print("""
|
||||||
|
❌ [Network][#\(requestId)][\(method) \(path)] 没有收到数据
|
||||||
|
⏱️ 耗时: \(duration)
|
||||||
|
""")
|
||||||
completion(.failure(.noData))
|
completion(.failure(.noData))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 打印响应数据(调试用)
|
// 打印响应数据(调试用)
|
||||||
if let responseString = String(data: data, encoding: .utf8) {
|
if let responseString = String(data: data, encoding: .utf8) {
|
||||||
print("📥 [Network] 响应数据: \(responseString)")
|
print("""
|
||||||
|
📥 [Network][#\(requestId)][\(method) \(path)] 响应数据:
|
||||||
|
\(responseString.prefix(1000))\(responseString.count > 1000 ? "..." : "")
|
||||||
|
""")
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
@ -152,17 +268,86 @@ class NetworkService {
|
|||||||
let result = try decoder.decode(T.self, from: data)
|
let result = try decoder.decode(T.self, from: data)
|
||||||
completion(.success(result))
|
completion(.success(result))
|
||||||
} catch let decodingError as DecodingError {
|
} catch let decodingError as DecodingError {
|
||||||
print("❌ [Network] JSON解析失败: \(decodingError.localizedDescription)")
|
print("""
|
||||||
if let dataString = String(data: data, encoding: .utf8) {
|
❌ [Network][#\(requestId)][\(method) \(path)] JSON解析失败
|
||||||
print("📋 [Network] 原始响应: \(dataString)")
|
🔍 错误: \(decodingError.localizedDescription)
|
||||||
}
|
📦 原始数据: \(String(data: data, encoding: .utf8) ?? "")
|
||||||
|
""")
|
||||||
completion(.failure(.decodingError(decodingError)))
|
completion(.failure(.decodingError(decodingError)))
|
||||||
} catch {
|
} catch {
|
||||||
print("❌ [Network] 未知错误: \(error.localizedDescription)")
|
print("""
|
||||||
|
❌ [Network][#\(requestId)][\(method) \(path)] 未知错误
|
||||||
|
🔍 错误: \(error.localizedDescription)
|
||||||
|
""")
|
||||||
completion(.failure(.unknownError(error)))
|
completion(.failure(.unknownError(error)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func refreshAndRetryRequests() {
|
||||||
|
guard !isRefreshing else { return }
|
||||||
|
|
||||||
|
isRefreshing = true
|
||||||
|
let refreshStartTime = Date()
|
||||||
|
|
||||||
|
print("🔄 [Network] 开始刷新Token...")
|
||||||
|
|
||||||
|
TokenManager.shared.refreshToken { [weak self] success, _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
let refreshDuration = String(format: "%.3fs", Date().timeIntervalSince(refreshStartTime))
|
||||||
|
|
||||||
|
if success {
|
||||||
|
print("""
|
||||||
|
✅ [Network] Token刷新成功
|
||||||
|
⏱️ 耗时: \(refreshDuration)
|
||||||
|
🔄 准备重试\(self.requestsToRetry.count)个请求...
|
||||||
|
""")
|
||||||
|
|
||||||
|
// 重试所有待处理的请求
|
||||||
|
let requestsToRetry = self.requestsToRetry
|
||||||
|
self.requestsToRetry.removeAll()
|
||||||
|
|
||||||
|
for (request, completion, requestId) in requestsToRetry {
|
||||||
|
var newRequest = request
|
||||||
|
if let token = KeychainHelper.getAccessToken() {
|
||||||
|
newRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
||||||
|
}
|
||||||
|
|
||||||
|
let task = URLSession.shared.dataTask(with: newRequest) { data, response, error in
|
||||||
|
if let data = data {
|
||||||
|
completion(.success(data))
|
||||||
|
} else if let error = error {
|
||||||
|
completion(.failure(.networkError(error)))
|
||||||
|
} else {
|
||||||
|
completion(.failure(.noData))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
task.resume()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print("""
|
||||||
|
❌ [Network] Token刷新失败
|
||||||
|
⏱️ 耗时: \(refreshDuration)
|
||||||
|
🚪 清除登录状态...
|
||||||
|
""")
|
||||||
|
|
||||||
|
// 清除token并通知需要重新登录
|
||||||
|
TokenManager.shared.clearTokens()
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
NotificationCenter.default.post(name: .userDidLogoutNotification, object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所有待处理的请求都返回未授权错误
|
||||||
|
self.requestsToRetry.forEach { _, completion, _ in
|
||||||
|
completion(.failure(.unauthorized))
|
||||||
|
}
|
||||||
|
self.requestsToRetry.removeAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.isRefreshing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - 公共方法
|
// MARK: - 公共方法
|
||||||
|
|
||||||
/// GET 请求
|
/// GET 请求
|
||||||
@ -185,6 +370,20 @@ class NetworkService {
|
|||||||
request("POST", path: path, parameters: parameters, headers: headers, completion: completion)
|
request("POST", path: path, parameters: parameters, headers: headers, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// POST 请求(带Token)
|
||||||
|
func postWithToken<T: Decodable>(
|
||||||
|
path: String,
|
||||||
|
parameters: [String: Any]? = nil,
|
||||||
|
headers: [String: String]? = nil,
|
||||||
|
completion: @escaping (Result<T, NetworkError>) -> Void
|
||||||
|
) {
|
||||||
|
var headers = headers ?? [:]
|
||||||
|
if let token = KeychainHelper.getAccessToken() {
|
||||||
|
headers["Authorization"] = "Bearer \(token)"
|
||||||
|
}
|
||||||
|
post(path: path, parameters: parameters, headers: headers, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
/// DELETE 请求
|
/// DELETE 请求
|
||||||
func delete<T: Decodable>(
|
func delete<T: Decodable>(
|
||||||
path: String,
|
path: String,
|
||||||
|
|||||||
@ -202,7 +202,7 @@ class TokenManager {
|
|||||||
let paddingLength = requiredLength - length
|
let paddingLength = requiredLength - length
|
||||||
if paddingLength > 0 {
|
if paddingLength > 0 {
|
||||||
let padding = "".padding(toLength: Int(paddingLength), withPad: "=", startingAt: 0)
|
let padding = "".padding(toLength: Int(paddingLength), withPad: "=", startingAt: 0)
|
||||||
base64 += padding
|
base64 = base64 + padding
|
||||||
}
|
}
|
||||||
|
|
||||||
return Data(base64Encoded: base64)
|
return Data(base64Encoded: base64)
|
||||||
@ -212,7 +212,7 @@ class TokenManager {
|
|||||||
/// - Parameter completion: 刷新完成回调
|
/// - Parameter completion: 刷新完成回调
|
||||||
/// - success: 是否刷新成功
|
/// - success: 是否刷新成功
|
||||||
/// - error: 错误信息(如果有)
|
/// - error: 错误信息(如果有)
|
||||||
private func refreshToken(completion: @escaping (Bool, Error?) -> Void) {
|
func refreshToken(completion: @escaping (Bool, Error?) -> Void) {
|
||||||
// 获取刷新令牌
|
// 获取刷新令牌
|
||||||
guard let refreshToken = KeychainHelper.getRefreshToken(), !refreshToken.isEmpty else {
|
guard let refreshToken = KeychainHelper.getRefreshToken(), !refreshToken.isEmpty else {
|
||||||
// 没有可用的刷新令牌
|
// 没有可用的刷新令牌
|
||||||
@ -232,7 +232,7 @@ class TokenManager {
|
|||||||
]
|
]
|
||||||
|
|
||||||
// 发送刷新请求
|
// 发送刷新请求
|
||||||
NetworkService.shared.postWithToken(path: "/v1/iam/access-token-refresh", parameters: parameters) {
|
NetworkService.shared.post(path: "/v1/iam/access-token-refresh", parameters: parameters) {
|
||||||
(result: Result<TokenResponse, NetworkError>) in
|
(result: Result<TokenResponse, NetworkError>) in
|
||||||
|
|
||||||
switch result {
|
switch result {
|
||||||
@ -312,109 +312,6 @@ private struct IdentityCheckResponse: Codable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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: - 通知名称
|
// MARK: - 通知名称
|
||||||
/// 定义应用中使用的通知名称
|
/// 定义应用中使用的通知名称
|
||||||
extension Notification.Name {
|
extension Notification.Name {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import SwiftUI
|
|||||||
import AuthenticationServices
|
import AuthenticationServices
|
||||||
import Alamofire
|
import Alamofire
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
|
import Foundation
|
||||||
|
|
||||||
/// 主登录视图 - 处理苹果登录
|
/// 主登录视图 - 处理苹果登录
|
||||||
struct LoginView: View {
|
struct LoginView: View {
|
||||||
@ -146,9 +147,6 @@ struct LoginView: View {
|
|||||||
|
|
||||||
private func handleAppleSignIn(result: Result<ASAuthorization, Error>) {
|
private func handleAppleSignIn(result: Result<ASAuthorization, Error>) {
|
||||||
print("🔵 [Apple Sign In] 开始处理登录结果...")
|
print("🔵 [Apple Sign In] 开始处理登录结果...")
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.isLoggedIn = true
|
|
||||||
}
|
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let authResults):
|
case .success(let authResults):
|
||||||
print("✅ [Apple Sign In] 登录授权成功")
|
print("✅ [Apple Sign In] 登录授权成功")
|
||||||
@ -195,12 +193,6 @@ struct LoginView: View {
|
|||||||
|
|
||||||
print("🔵 [Apple ID] 准备调用后端认证...")
|
print("🔵 [Apple ID] 准备调用后端认证...")
|
||||||
authenticateWithBackend(
|
authenticateWithBackend(
|
||||||
userId: appleIDCredential.user,
|
|
||||||
email: appleIDCredential.email ?? "",
|
|
||||||
name: [appleIDCredential.fullName?.givenName,
|
|
||||||
appleIDCredential.fullName?.familyName]
|
|
||||||
.compactMap { $0 }
|
|
||||||
.joined(separator: " "),
|
|
||||||
identityToken: identityToken,
|
identityToken: identityToken,
|
||||||
authCode: authCode
|
authCode: authCode
|
||||||
)
|
)
|
||||||
@ -209,169 +201,49 @@ struct LoginView: View {
|
|||||||
// MARK: - Network
|
// MARK: - Network
|
||||||
|
|
||||||
private func authenticateWithBackend(
|
private func authenticateWithBackend(
|
||||||
userId: String,
|
|
||||||
email: String,
|
|
||||||
name: String,
|
|
||||||
identityToken: String,
|
identityToken: String,
|
||||||
authCode: String?
|
authCode: String?
|
||||||
) {
|
) {
|
||||||
isLoading = true
|
isLoading = true
|
||||||
print("🔵 [Backend] 开始后端认证...")
|
print("🔵 [Backend] 开始后端认证...")
|
||||||
|
|
||||||
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] = [
|
||||||
"provider": "Apple",
|
|
||||||
"token": identityToken,
|
"token": identityToken,
|
||||||
"userId": userId,
|
"provider": "Apple",
|
||||||
"email": email,
|
|
||||||
"name": name,
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if let authCode = authCode {
|
if let authCode = authCode {
|
||||||
parameters["authorization_code"] = authCode
|
parameters["authorization_code"] = authCode
|
||||||
}
|
}
|
||||||
|
|
||||||
print("📤 [Backend] 请求URL: \(endpoint)")
|
NetworkService.shared.post(
|
||||||
print("📤 [Backend] 请求参数: \(parameters)")
|
path: "/iam/login/oauth",
|
||||||
|
parameters: parameters
|
||||||
var request = URLRequest(url: url)
|
) { (result: Result<AuthResponse, NetworkError>) in
|
||||||
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 {
|
DispatchQueue.main.async {
|
||||||
self.isLoading = false
|
switch result {
|
||||||
|
case .success(let authResponse):
|
||||||
// 1. 处理网络错误
|
print("✅ [Backend] 认证成功")
|
||||||
if let error = error {
|
|
||||||
print("❌ [Backend] 请求失败: \(error.localizedDescription)")
|
// 保存token等认证信息
|
||||||
self.handleAuthenticationError(error)
|
if let loginInfo = authResponse.data?.userLoginInfo {
|
||||||
return
|
KeychainHelper.saveAccessToken(loginInfo.accessToken)
|
||||||
}
|
KeychainHelper.saveRefreshToken(loginInfo.refreshToken)
|
||||||
|
// 可以在这里保存其他用户信息,如userId, nickname等
|
||||||
// 2. 检查响应
|
print("👤 用户ID: \(loginInfo.userId)")
|
||||||
guard let httpResponse = response as? HTTPURLResponse else {
|
print("👤 昵称: \(loginInfo.nickname)")
|
||||||
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. 解析响应数据
|
self.isLoggedIn = true
|
||||||
do {
|
|
||||||
// 首先解析顶层 JSON
|
case .failure(let error):
|
||||||
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
print("❌ [Backend] 认证失败: \(error.localizedDescription)")
|
||||||
print("✅ [Backend] 响应解析成功")
|
self.errorMessage = error.localizedDescription
|
||||||
print("📦 [Backend] 响应内容: \(json)")
|
self.showError = true
|
||||||
|
self.isLoading = false
|
||||||
// 检查状态码
|
|
||||||
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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user