Files
e-party-iOS/yana/Features/RecoverPasswordFeature.swift
edwinQQQ e45ad3bad5 feat: 增强邮箱登录功能和密码恢复流程
- 更新邮箱登录相关功能,新增邮箱验证码获取和登录API端点。
- 添加AccountModel以管理用户认证信息,支持会话票据的存储和更新。
- 实现密码恢复功能,支持通过邮箱获取验证码和重置密码。
- 增加本地化支持,更新相关字符串以适应新功能。
- 引入ValidationHelper以验证邮箱和密码格式,确保用户输入的有效性。
- 更新视图以支持邮箱登录和密码恢复的用户交互。
2025-07-10 14:00:58 +08:00

274 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 RecoverPasswordFeature {
@ObservableState
struct State: Equatable {
var email: String = ""
var verificationCode: String = ""
var newPassword: String = ""
var isCodeLoading: Bool = false
var isResetLoading: Bool = false
var errorMessage: String? = nil
var isCodeSent: Bool = false
#if DEBUG
init() {
self.email = "exzero@126.com"
self.verificationCode = ""
self.newPassword = ""
}
#endif
}
enum Action {
case emailChanged(String)
case verificationCodeChanged(String)
case newPasswordChanged(String)
case getVerificationCodeTapped
case getCodeResponse(Result<EmailGetCodeResponse, Error>)
case resetPasswordTapped
case resetPasswordResponse(Result<ResetPasswordResponse, Error>)
case resetState
}
@Dependency(\.apiService) var apiService
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .emailChanged(let email):
state.email = email
state.errorMessage = nil
return .none
case .verificationCodeChanged(let code):
state.verificationCode = code
state.errorMessage = nil
return .none
case .newPasswordChanged(let password):
state.newPassword = password
state.errorMessage = nil
return .none
case .getVerificationCodeTapped:
guard !state.email.isEmpty else {
state.errorMessage = "recover_password.email_required".localized
return .none
}
guard ValidationHelper.isValidEmail(state.email) else {
state.errorMessage = "recover_password.invalid_email".localized
return .none
}
state.isCodeLoading = true
state.isCodeSent = false
state.errorMessage = nil
return .run { [email = state.email] send in
do {
guard let request = RecoverPasswordHelper.createEmailGetCodeRequest(email: email) else {
await send(.getCodeResponse(.failure(APIError.encryptionFailed)))
return
}
let response = try await apiService.request(request)
await send(.getCodeResponse(.success(response)))
} catch {
await send(.getCodeResponse(.failure(error)))
}
}
case .getCodeResponse(.success(let response)):
state.isCodeLoading = false
if response.isSuccess {
state.isCodeSent = true
return .none
} else {
state.errorMessage = response.errorMessage
return .none
}
case .getCodeResponse(.failure(let error)):
state.isCodeLoading = false
if let apiError = error as? APIError {
state.errorMessage = apiError.localizedDescription
} else {
state.errorMessage = "recover_password.code_send_failed".localized
}
return .none
case .resetPasswordTapped:
guard !state.email.isEmpty && !state.verificationCode.isEmpty && !state.newPassword.isEmpty else {
state.errorMessage = "recover_password.fields_required".localized
return .none
}
guard ValidationHelper.isValidEmail(state.email) else {
state.errorMessage = "recover_password.invalid_email".localized
return .none
}
guard ValidationHelper.isValidPassword(state.newPassword) else {
state.errorMessage = "recover_password.invalid_password".localized
return .none
}
state.isResetLoading = true
state.errorMessage = nil
return .run { [email = state.email, code = state.verificationCode, password = state.newPassword] send in
do {
guard let request = RecoverPasswordHelper.createResetPasswordRequest(
email: email,
code: code,
newPassword: password
) else {
await send(.resetPasswordResponse(.failure(APIError.encryptionFailed)))
return
}
let response = try await apiService.request(request)
await send(.resetPasswordResponse(.success(response)))
} catch {
await send(.resetPasswordResponse(.failure(error)))
}
}
case .resetPasswordResponse(.success(let response)):
state.isResetLoading = false
if response.isSuccess {
//
state.errorMessage = nil
return .none
} else {
state.errorMessage = response.errorMessage
return .none
}
case .resetPasswordResponse(.failure(let error)):
state.isResetLoading = false
if let apiError = error as? APIError {
state.errorMessage = apiError.localizedDescription
} else {
state.errorMessage = "recover_password.reset_failed".localized
}
return .none
case .resetState:
state.email = ""
state.verificationCode = ""
state.newPassword = ""
state.isCodeLoading = false
state.isResetLoading = false
state.errorMessage = nil
state.isCodeSent = false
return .none
}
}
}
}
// MARK: - Password Reset API Models
///
struct ResetPasswordResponse: Codable, Equatable {
let status: String?
let message: String?
let code: Int?
let data: String?
///
var isSuccess: Bool {
return code == 200 || status?.lowercased() == "success"
}
///
var errorMessage: String {
return message ?? "recover_password.reset_failed".localized
}
}
///
struct ResetPasswordRequest: APIRequestProtocol {
typealias Response = ResetPasswordResponse
let endpoint = "/password/reset" //
let method: HTTPMethod = .POST
let includeBaseParameters = true
let queryParameters: [String: String]?
let bodyParameters: [String: Any]? = nil
let timeout: TimeInterval = 30.0
///
/// - Parameters:
/// - email: DES
/// - code:
/// - newPassword: DES
init(email: String, code: String, newPassword: String) {
self.queryParameters = [
"email": email,
"code": code,
"newPassword": newPassword
]
}
}
// MARK: - Recover Password Helper
struct RecoverPasswordHelper {
///
/// - Parameter email:
/// - Returns: APInil
static func createEmailGetCodeRequest(email: String) -> EmailGetCodeRequest? {
let encryptionKey = "1ea53d260ecf11e7b56e00163e046a26"
guard let encryptedEmail = DESEncrypt.encryptUseDES(email, key: encryptionKey) else {
print("❌ 邮箱DES加密失败")
return nil
}
print("🔐 密码恢复邮箱DES加密成功")
print(" 原始邮箱: \(email)")
print(" 加密邮箱: \(encryptedEmail)")
// 使type=3
return EmailGetCodeRequest(emailAddress: email, type: 3)
}
///
/// - Parameters:
/// - email:
/// - code:
/// - newPassword:
/// - Returns: APInil
static func createResetPasswordRequest(email: String, code: String, newPassword: String) -> ResetPasswordRequest? {
let encryptionKey = "1ea53d260ecf11e7b56e00163e046a26"
guard let encryptedEmail = DESEncrypt.encryptUseDES(email, key: encryptionKey),
let encryptedPassword = DESEncrypt.encryptUseDES(newPassword, key: encryptionKey) else {
print("❌ 密码重置DES加密失败")
return nil
}
print("🔐 密码重置DES加密成功")
print(" 原始邮箱: \(email)")
print(" 加密邮箱: \(encryptedEmail)")
print(" 验证码: \(code)")
print(" 原始新密码: \(newPassword)")
print(" 加密新密码: \(encryptedPassword)")
return ResetPasswordRequest(
email: encryptedEmail,
code: code,
newPassword: encryptedPassword
)
}
}