feat: 增强邮箱登录功能和密码恢复流程

- 更新邮箱登录相关功能,新增邮箱验证码获取和登录API端点。
- 添加AccountModel以管理用户认证信息,支持会话票据的存储和更新。
- 实现密码恢复功能,支持通过邮箱获取验证码和重置密码。
- 增加本地化支持,更新相关字符串以适应新功能。
- 引入ValidationHelper以验证邮箱和密码格式,确保用户输入的有效性。
- 更新视图以支持邮箱登录和密码恢复的用户交互。
This commit is contained in:
edwinQQQ
2025-07-10 14:00:58 +08:00
parent c470dba79c
commit e45ad3bad5
23 changed files with 2054 additions and 164 deletions

View File

@@ -0,0 +1,274 @@
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
)
}
}