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

497 lines
16 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
// MARK: - HTTP Method
/// HTTP
///
/// API HTTP
/// URLRequest
enum HTTPMethod: String, CaseIterable {
case GET = "GET"
case POST = "POST"
case PUT = "PUT"
case DELETE = "DELETE"
case PATCH = "PATCH"
}
// MARK: - API Error Types
/// API
///
/// API
/// 便
///
///
/// -
/// -
/// - HTTP
/// -
enum APIError: Error, Equatable {
case invalidURL
case noData
case decodingError(String)
case networkError(String)
case httpError(statusCode: Int, message: String?)
case timeout
case resourceTooLarge
case encryptionFailed //
case invalidResponse //
case ticketFailed //
case custom(String) //
case unknown(String)
var localizedDescription: String {
switch self {
case .invalidURL:
return "无效的 URL"
case .noData:
return "没有收到数据"
case .decodingError(let message):
return "数据解析失败: \(message)"
case .networkError(let message):
return "网络错误: \(message)"
case .httpError(let statusCode, let message):
return "HTTP 错误 \(statusCode): \(message ?? "未知错误")"
case .timeout:
return "请求超时"
case .resourceTooLarge:
return "响应数据过大"
case .encryptionFailed:
return "数据加密失败"
case .invalidResponse:
return "服务器响应无效"
case .ticketFailed:
return "获取会话票据失败"
case .custom(let message):
return message
case .unknown(let message):
return "未知错误: \(message)"
}
}
}
// MARK: - Base Request Parameters
///
///
/// API
/// API
///
///
/// -
/// -
/// -
///
/// 使
/// ```swift
/// var baseRequest = BaseRequest()
/// baseRequest.generateSignature(with: ["key": "value"])
/// ```
struct BaseRequest: Codable {
let acceptLanguage: String
let os: String = "iOS"
let osVersion: String
let netType: Int
let ispType: String
let channel: String
let model: String
let deviceId: String
let appVersion: String
let app: String
let lang: String
let mcc: String?
let spType: String?
var pubSign: String
enum CodingKeys: String, CodingKey {
case acceptLanguage = "Accept-Language"
case os, osVersion, netType, ispType, channel, model, deviceId
case appVersion, app, lang, mcc, spType
case pubSign = "pub_sign"
}
init() {
//
let preferredLanguage = Locale.current.languageCode ?? "en"
self.acceptLanguage = preferredLanguage
self.lang = preferredLanguage
//
self.osVersion = UIDevice.current.systemVersion
//
self.model = UIDevice.current.model
// ID
self.deviceId = UIDevice.current.identifierForVendor?.uuidString ?? UUID().uuidString
//
self.appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0"
//
self.app = Bundle.main.infoDictionary?["CFBundleName"] as? String ?? "eparty"
// WiFi=2, =1
self.netType = NetworkTypeDetector.getCurrentNetworkType()
//
let carrierInfo = CarrierInfoManager.getCarrierInfo()
self.ispType = carrierInfo.ispType
self.mcc = carrierInfo.mcc == "65535" ? nil : carrierInfo.mcc
self.spType = self.mcc
//
#if DEBUG
self.channel = "molistar_enterprise"
#else
self.channel = "appstore"
#endif
//
self.pubSign = "" //
}
/// API
///
///
/// 1.
/// 2.
/// 3. key
/// 4.
/// 5. MD5
///
/// - Parameter requestParams:
mutating func generateSignature(with requestParams: [String: Any] = [:]) {
// 1.
var allParams = requestParams
//
allParams["Accept-Language"] = self.acceptLanguage
allParams["os"] = self.os
allParams["osVersion"] = self.osVersion
allParams["netType"] = self.netType
allParams["ispType"] = self.ispType
allParams["channel"] = self.channel
allParams["model"] = self.model
allParams["deviceId"] = self.deviceId
allParams["appVersion"] = self.appVersion
allParams["app"] = self.app
allParams["lang"] = self.lang
if let mcc = self.mcc {
allParams["mcc"] = mcc
}
if let spType = self.spType {
allParams["spType"] = spType
}
// 2. API rule
let systemParams = [
"Accept-Language", "pub_uid", "appVersion", "appVersionCode",
"channel", "deviceId", "ispType", "netType", "os",
"osVersion", "app", "ticket", "client", "lang", "mcc"
]
var filteredParams = allParams
for param in systemParams {
filteredParams.removeValue(forKey: param)
}
// 3. key
// "key0=value0&key1=value1&key2=value2"
let sortedKeys = filteredParams.keys.sorted()
let paramString = sortedKeys.map { key in
"\(key)=\(String(describing: filteredParams[key] ?? ""))"
}.joined(separator: "&")
// 4.
let keyString = "key=rpbs6us1m8r2j9g6u06ff2bo18orwaya"
let finalString = paramString.isEmpty ? keyString : "\(paramString)&\(keyString)"
// 5. MD5
self.pubSign = finalString.md5().uppercased()
}
}
// MARK: - Network Type Detector
struct NetworkTypeDetector {
static func getCurrentNetworkType() -> Int {
// WiFi = 2, = 1
//
return 2 //
}
}
// MARK: - Carrier Info Manager
struct CarrierInfoManager {
struct CarrierInfo {
let ispType: String
let mcc: String?
}
static func getCarrierInfo() -> CarrierInfo {
//
return CarrierInfo(ispType: "65535", mcc: nil)
}
}
// MARK: - User Info Manager (for Headers)
struct UserInfoManager {
private static let userDefaults = UserDefaults.standard
// MARK: - Storage Keys
private enum StorageKeys {
static let userId = "user_id"
static let accessToken = "access_token"
static let ticket = "user_ticket"
static let userInfo = "user_info"
static let accountModel = "account_model" // AccountModel
}
// MARK: - User ID Management
static func getCurrentUserId() -> String? {
return userDefaults.string(forKey: StorageKeys.userId)
}
static func saveUserId(_ userId: String) {
userDefaults.set(userId, forKey: StorageKeys.userId)
userDefaults.synchronize()
print("💾 保存用户ID: \(userId)")
}
// MARK: - Access Token Management
static func getAccessToken() -> String? {
return userDefaults.string(forKey: StorageKeys.accessToken)
}
static func saveAccessToken(_ accessToken: String) {
userDefaults.set(accessToken, forKey: StorageKeys.accessToken)
userDefaults.synchronize()
print("💾 保存 Access Token")
}
// MARK: - Ticket Management ()
private static var currentTicket: String?
static func getCurrentUserTicket() -> String? {
return currentTicket
}
static func saveTicket(_ ticket: String) {
currentTicket = ticket
print("💾 保存 Ticket 到内存")
}
static func clearTicket() {
currentTicket = nil
print("🗑️ 清除 Ticket")
}
// MARK: - User Info Management
static func saveUserInfo(_ userInfo: UserInfo) {
do {
let data = try JSONEncoder().encode(userInfo)
userDefaults.set(data, forKey: StorageKeys.userInfo)
userDefaults.synchronize()
// ID
if let userId = userInfo.userId {
saveUserId(userId)
}
print("💾 保存用户信息成功")
} catch {
print("❌ 保存用户信息失败: \(error)")
}
}
static func getUserInfo() -> UserInfo? {
guard let data = userDefaults.data(forKey: StorageKeys.userInfo) else {
return nil
}
do {
return try JSONDecoder().decode(UserInfo.self, from: data)
} catch {
print("❌ 解析用户信息失败: \(error)")
return nil
}
}
// MARK: - Complete Authentication Data Management
/// OAuth Token + Ticket +
static func saveCompleteAuthenticationData(
accessToken: String,
ticket: String,
uid: Int?, // String?Int?
userInfo: UserInfo?
) {
saveAccessToken(accessToken)
saveTicket(ticket)
if let uid = uid {
saveUserId("\(uid)") //
}
if let userInfo = userInfo {
saveUserInfo(userInfo)
}
print("✅ 完整认证信息保存成功")
}
///
static func hasValidAuthentication() -> Bool {
return getAccessToken() != nil && getCurrentUserTicket() != nil
}
///
static func clearAllAuthenticationData() {
userDefaults.removeObject(forKey: StorageKeys.userId)
userDefaults.removeObject(forKey: StorageKeys.accessToken)
userDefaults.removeObject(forKey: StorageKeys.userInfo)
clearAccountModel() // AccountModel
clearTicket()
userDefaults.synchronize()
print("🗑️ 清除所有认证信息")
}
/// Ticket
static func restoreTicketIfNeeded() async -> Bool {
guard let accessToken = getAccessToken(),
getCurrentUserTicket() == nil else {
return false
}
print("🔄 尝试使用 Access Token 恢复 Ticket...")
// APIService false
// TicketHelper.createTicketRequest
return false
}
// MARK: - Account Model Management
/// AccountModel
/// - Parameter accountModel:
static func saveAccountModel(_ accountModel: AccountModel) {
do {
let data = try JSONEncoder().encode(accountModel)
userDefaults.set(data, forKey: StorageKeys.accountModel)
userDefaults.synchronize()
//
if let uid = accountModel.uid {
saveUserId(uid)
}
if let accessToken = accountModel.accessToken {
saveAccessToken(accessToken)
}
if let ticket = accountModel.ticket {
saveTicket(ticket)
}
print("💾 AccountModel 保存成功")
} catch {
print("❌ AccountModel 保存失败: \(error)")
}
}
/// AccountModel
/// - Returns: nil
static func getAccountModel() -> AccountModel? {
guard let data = userDefaults.data(forKey: StorageKeys.accountModel) else {
return nil
}
do {
return try JSONDecoder().decode(AccountModel.self, from: data)
} catch {
print("❌ AccountModel 解析失败: \(error)")
return nil
}
}
/// AccountModel ticket
/// - Parameter ticket:
static func updateAccountModelTicket(_ ticket: String) {
guard var accountModel = getAccountModel() else {
print("❌ 无法更新 ticketAccountModel 不存在")
return
}
accountModel.ticket = ticket
saveAccountModel(accountModel)
saveTicket(ticket) // ticket
}
/// AccountModel
/// - Returns:
static func hasValidAccountModel() -> Bool {
guard let accountModel = getAccountModel() else {
return false
}
return accountModel.hasValidAuthentication
}
/// AccountModel
static func clearAccountModel() {
userDefaults.removeObject(forKey: StorageKeys.accountModel)
userDefaults.synchronize()
print("🗑️ AccountModel 已清除")
}
}
// MARK: - API Request Protocol
/// API
///
/// API
/// API
///
///
/// - Response:
/// - endpoint: API
/// - method: HTTP
/// -
///
/// 使
/// ```swift
/// struct LoginRequest: APIRequestProtocol {
/// typealias Response = LoginResponse
/// let endpoint = "/auth/login"
/// let method: HTTPMethod = .POST
/// // ...
/// }
/// ```
protocol APIRequestProtocol {
associatedtype Response: Codable
var endpoint: String { get }
var method: HTTPMethod { get }
var queryParameters: [String: String]? { get }
var bodyParameters: [String: Any]? { get }
var headers: [String: String]? { get }
var customHeaders: [String: String]? { get } //
var timeout: TimeInterval { get }
var includeBaseParameters: Bool { get }
}
extension APIRequestProtocol {
var timeout: TimeInterval { 30.0 }
var includeBaseParameters: Bool { true }
var headers: [String: String]? { nil }
var customHeaders: [String: String]? { nil } //
}
// MARK: - Generic API Response
struct APIResponse<T: Codable>: Codable {
let data: T?
let status: String?
let message: String?
let code: Int?
}
// String+MD5 Utils/Extensions/String+MD5.swift