Files
e-party-iOS/yana/Features/LoginFeature.swift
edwinQQQ 9a49d591c3 feat: 添加腾讯云COS Token管理功能及相关视图更新
- 在APIEndpoints.swift中新增tcToken端点以支持腾讯云COS Token获取。
- 在APIModels.swift中新增TcTokenRequest和TcTokenResponse模型,处理Token请求和响应。
- 在COSManager.swift中实现Token的获取、缓存和过期管理逻辑,提升API请求的安全性。
- 在LanguageSettingsView中添加调试功能,允许测试COS Token获取。
- 在多个视图中更新状态管理和导航逻辑,确保用户体验一致性。
- 在FeedFeature和HomeFeature中优化状态管理,简化视图逻辑。
2025-07-18 20:50:25 +08:00

255 lines
10 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() //
// HomeFeature
var homeState = HomeFeature.State()
// Account Model Ticket
var accountModel: AccountModel?
var isTicketLoading = false
var ticketError: String?
var loginStep: LoginStep = .initial
// -
var isInitialized = false
// true
var isAnyLoginCompleted: Bool {
idLoginState.loginStep == .completed || emailLoginState.loginStep == .completed
}
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 onAppear
case updateAccount(String)
case updatePassword(String)
case login
case loginResponse(TaskResult<IDLoginResponse>)
case idLogin(IDLoginFeature.Action)
case emailLogin(EMailLoginFeature.Action) // action
// HomeFeature action
case home(HomeFeature.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()
}
// HomeFeature
Scope(state: \.homeState, action: \.home) {
HomeFeature()
}
Reduce { state, action in
switch action {
case .onAppear:
//
guard !state.isInitialized else {
debugInfoSync("🚀 LoginFeature: 已初始化,跳过重复执行")
return .none
}
state.isInitialized = true
debugInfoSync("🚀 LoginFeature: 首次初始化")
//
return .none
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 = await 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
debugInfoSync("✅ OAuth 认证成功")
debugInfoSync("🔑 Access Token: \(accountModel.accessToken ?? "nil")")
debugInfoSync("🆔 用户 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 {
debugErrorSync("❌ 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
debugInfoSync("✅ 完整登录流程成功")
debugInfoSync("🎫 Ticket 获取成功: \(response.ticket ?? "nil")")
// AccountModel ticket
if let ticket = response.ticket {
if let oldAccountModel = state.accountModel {
let newAccountModel = oldAccountModel.withTicket(ticket)
state.accountModel = newAccountModel
// Effect AccountModel
return .run { _ in
await UserInfoManager.saveAccountModel(newAccountModel)
}
} else {
debugErrorSync("❌ 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
debugErrorSync("❌ 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
// Effect
return .run { _ in
await UserInfoManager.clearAllAuthenticationData()
}
case .idLogin:
// IDLoginfeature
return .none
case .emailLogin:
// EmailLoginfeature
return .none
case .home(_):
return .none
}
}
}
}
// 使
// extension Notification.Name {
// static let ticketSuccess = Notification.Name("ticketSuccess")
// }