Files
e-party-iOS/yana/Features/LoginFeature.swift
edwinQQQ f9f3dec53f feat: 更新Podfile和Podfile.lock,移除Alamofire依赖并添加API认证机制文档
- 注释掉Podfile中的Alamofire依赖,更新Podfile.lock以反映更改。
- 在yana/APIs/API-README.md中新增自动认证Header机制的详细文档,描述其工作原理、实现细节及最佳实践。
- 在yana/yanaApp.swift中将print语句替换为debugInfo以增强调试信息的输出。
- 在API相关文件中实现用户认证状态检查和相关header的自动添加逻辑,提升API请求的安全性和用户体验。
- 更新多个文件中的日志输出,确保在DEBUG模式下提供详细的调试信息。
2025-07-11 16:53:46 +08:00

223 lines
9.0 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Foundation
import ComposableArchitecture
@Reducer
struct LoginFeature {
@ObservableState
struct State: Equatable {
var account: String = ""
var password: String = ""
var isLoading = false
var error: String?
var idLoginState = IDLoginFeature.State()
var emailLoginState = EMailLoginFeature.State() //
// Account Model Ticket
var accountModel: AccountModel?
var isTicketLoading = false
var ticketError: String?
var loginStep: LoginStep = .initial
enum LoginStep: Equatable {
case initial //
case authenticating // OAuth
case gettingTicket // Ticket
case completed //
case failed //
}
#if DEBUG
init() {
//
self.account = ""
self.password = ""
}
#endif
}
enum Action {
case updateAccount(String)
case updatePassword(String)
case login
case loginResponse(TaskResult<IDLoginResponse>)
case idLogin(IDLoginFeature.Action)
case emailLogin(EMailLoginFeature.Action) // action
// Ticket actions
case requestTicket(accessToken: String)
case ticketResponse(TaskResult<TicketResponse>)
case clearTicketError
case resetLogin
}
@Dependency(\.apiService) var apiService
var body: some ReducerOf<Self> {
Scope(state: \.idLoginState, action: \.idLogin) {
IDLoginFeature()
}
Scope(state: \.emailLoginState, action: \.emailLogin) {
EMailLoginFeature()
}
Reduce { state, action in
switch action {
case let .updateAccount(account):
state.account = account
return .none
case let .updatePassword(password):
state.password = password
return .none
case .login:
state.isLoading = true
state.error = nil
state.ticketError = nil
state.loginStep = .authenticating
// 使accountpassword
return .run { [account = state.account, password = state.password] send in
do {
// 使LoginHelper
guard let loginRequest = LoginHelper.createIDLoginRequest(userID: account, password: password) else {
await send(.loginResponse(.failure(APIError.decodingError("加密失败"))))
return
}
//
let response = try await apiService.request(loginRequest)
await send(.loginResponse(.success(response)))
} catch {
if let apiError = error as? APIError {
await send(.loginResponse(.failure(apiError)))
} else {
await send(.loginResponse(.failure(APIError.unknown(error.localizedDescription))))
}
}
}
case let .loginResponse(.success(response)):
state.isLoading = false
if response.isSuccess {
// OAuth
state.error = nil
// AccountModel
if let loginData = response.data,
let accountModel = AccountModel.from(loginData: loginData) {
state.accountModel = accountModel
debugInfo("✅ OAuth 认证成功")
debugInfo("🔑 Access Token: \(accountModel.accessToken ?? "nil")")
debugInfo("🆔 用户 UID: \(accountModel.uid ?? "nil")")
// ticket
return .send(.requestTicket(accessToken: accountModel.accessToken!))
} else {
state.error = "登录数据格式错误"
state.loginStep = .failed
}
} else {
state.error = response.errorMessage
state.loginStep = .failed
}
return .none
case let .loginResponse(.failure(error)):
state.isLoading = false
state.error = error.localizedDescription
state.loginStep = .failed
return .none
case let .requestTicket(accessToken):
state.isTicketLoading = true
state.ticketError = nil
state.loginStep = .gettingTicket
return .run { [accountModel = state.accountModel] send in
do {
// AccountModel uid Int
let uid = accountModel?.uid != nil ? Int(accountModel!.uid!) : nil
let ticketRequest = TicketHelper.createTicketRequest(accessToken: accessToken, uid: uid)
let response = try await apiService.request(ticketRequest)
await send(.ticketResponse(.success(response)))
} catch {
debugError("❌ Ticket 获取失败: \(error)")
await send(.ticketResponse(.failure(APIError.networkError(error.localizedDescription))))
}
}
case let .ticketResponse(.success(response)):
state.isTicketLoading = false
if response.isSuccess {
state.ticketError = nil
state.loginStep = .completed
debugInfo("✅ 完整登录流程成功")
debugInfo("🎫 Ticket 获取成功: \(response.ticket ?? "nil")")
// AccountModel ticket
if let ticket = response.ticket {
if var accountModel = state.accountModel {
accountModel.ticket = ticket
state.accountModel = accountModel
// AccountModel
UserInfoManager.saveAccountModel(accountModel)
// Ticket
NotificationCenter.default.post(name: .ticketSuccess, object: nil)
} else {
debugError("❌ AccountModel 不存在,无法保存 ticket")
state.ticketError = "内部错误:账户信息丢失"
state.loginStep = .failed
}
} else {
state.ticketError = "Ticket 为空"
state.loginStep = .failed
}
} else {
state.ticketError = response.errorMessage
state.loginStep = .failed
}
return .none
case let .ticketResponse(.failure(error)):
state.isTicketLoading = false
state.ticketError = error.localizedDescription
state.loginStep = .failed
debugError("❌ Ticket 获取失败: \(error.localizedDescription)")
return .none
case .clearTicketError:
state.ticketError = nil
return .none
case .resetLogin:
state.isLoading = false
state.isTicketLoading = false
state.error = nil
state.ticketError = nil
state.accountModel = nil // AccountModel
state.loginStep = .initial
//
UserInfoManager.clearAllAuthenticationData()
return .none
case .idLogin:
// IDLoginfeature
return .none
case .emailLogin:
// EmailLoginfeature
return .none
}
}
}
}