import Foundation // MARK: - Account Model /// 账户认证信息模型 /// 用于承接 oauth/token 和 oauth/ticket 接口的认证数据 /// 参照 OC 版本的 AccountModel 设计 struct AccountModel: Codable, Equatable { let uid: String? // 用户唯一标识 let jti: String? // JWT ID let tokenType: String? // Token 类型 (bearer) let refreshToken: String? // 刷新令牌 let netEaseToken: String? // 网易云信令牌 let accessToken: String? // OAuth 访问令牌 let expiresIn: Int? // 过期时间(秒) let scope: String? // 权限范围 var ticket: String? // 业务会话票据(来自 oauth/ticket) enum CodingKeys: String, CodingKey { case uid case jti case tokenType = "token_type" case refreshToken = "refresh_token" case netEaseToken case accessToken = "access_token" case expiresIn = "expires_in" case scope case ticket } /// 检查是否有有效的认证信息 var hasValidAuthentication: Bool { return accessToken != nil && !accessToken!.isEmpty } /// 检查是否有有效的业务会话 var hasValidSession: Bool { return hasValidAuthentication && ticket != nil && !ticket!.isEmpty } /// 从 IDLoginData 创建 AccountModel /// - Parameter loginData: 登录响应数据 /// - Returns: AccountModel 实例,如果数据无效则返回 nil static func from(loginData: IDLoginData) -> AccountModel? { // 确保至少有 accessToken 和 uid guard let accessToken = loginData.accessToken, let uid = loginData.uid else { return nil } return AccountModel( uid: String(uid), jti: loginData.jti, tokenType: loginData.tokenType, refreshToken: loginData.refreshToken, netEaseToken: loginData.netEaseToken, accessToken: accessToken, expiresIn: loginData.expiresIn, scope: loginData.scope, ticket: nil // 初始为空,后续通过 oauth/ticket 填充 ) } /// 更新 ticket 信息 /// - Parameter ticket: 从 oauth/ticket 获取的票据 /// - Returns: 更新后的 AccountModel func withTicket(_ ticket: String) -> AccountModel { var updatedModel = self updatedModel.ticket = ticket return updatedModel } } // MARK: - ID Login Request Model struct IDLoginAPIRequest: APIRequestProtocol { typealias Response = IDLoginResponse let endpoint = APIEndpoint.login.path // 使用枚举定义的登录端点 let method: HTTPMethod = .POST let includeBaseParameters = true var bodyParameters: [String: Any]? { nil } let timeout: TimeInterval = 30.0 // MARK: - Private Properties private let phone: String private let password: String private let clientSecret: String private let version: String private let clientId: String private let grantType: String // MARK: - Computed Properties var queryParameters: [String: String]? { return [ "phone": phone, "password": password, "client_secret": clientSecret, "version": version, "client_id": clientId, "grant_type": grantType ] } /// 初始化ID登录请求 /// - Parameters: /// - phone: DES加密后的用户ID/手机号 /// - password: DES加密后的密码 /// - clientSecret: 客户端密钥,固定为"uyzjdhds" /// - version: 版本号,固定为"1" /// - clientId: 客户端ID,固定为"erban-client" /// - grantType: 授权类型,固定为"password" init(phone: String, password: String, clientSecret: String = "uyzjdhds", version: String = "1", clientId: String = "erban-client", grantType: String = "password") { self.phone = phone self.password = password self.clientSecret = clientSecret self.version = version self.clientId = clientId self.grantType = grantType } } // MARK: - ID Login Response Model struct IDLoginResponse: Codable, Equatable { let status: String? let message: String? let code: Int? let data: IDLoginData? /// 是否登录成功 var isSuccess: Bool { return code == 200 || status?.lowercased() == "success" } /// 错误消息(如果有) var errorMessage: String { return message ?? "登录失败,请重试" } } // MARK: - ID Login Data Model struct IDLoginData: Codable, Equatable { let accessToken: String? let refreshToken: String? let tokenType: String? let expiresIn: Int? let scope: String? let userInfo: UserInfo? let uid: Int? // 修改:从String?改为Int?以匹配API返回 let netEaseToken: String? // 新增:网易云token let jti: String? // 新增:JWT token identifier enum CodingKeys: String, CodingKey { case accessToken = "access_token" case refreshToken = "refresh_token" case tokenType = "token_type" case expiresIn = "expires_in" case scope case userInfo = "user_info" case uid case netEaseToken case jti } } // MARK: - User Info Model struct UserInfo: Codable, Equatable { let uid: Int? let userId: String? // 兼容旧字段 let nick: String? let nickname: String? // 兼容旧字段 let avatar: String? let region: String? let regionDesc: String? let gender: Int? let birth: Int64? let userDesc: String? let userLevelVo: UserLevelVo? let userVipInfoVO: UserVipInfoVO? let medalsPic: [MedalsPic]? let userHeadwear: UserHeadwear? let privatePhoto: [PrivatePhoto]? let createTime: Int64? let phoneAreaCode: String? let erbanNo: Int? let isCertified: Bool? let isBindPhone: Bool? let isBindApple: Bool? let isBindPasswd: Bool? let isBindPaymentPwd: Bool? let banAccount: Bool? let visitNum: Int? let fansNum: Int? let followNum: Int? let visitHide: Bool? let visitListView: Bool? let newUser: Bool? let defUser: Int? let platformRole: Int? let bindType: Int? let showLimitCharge: Bool? let uploadGifAvatarPrice: Int? let hasRegPacket: Bool? let hasPrettyErbanNo: Bool? let hasSuperRole: Bool? let isRechargeUser: Bool? let isFirstCharge: Bool? let fromSayHelloChannel: Bool? let partitionId: Int? let useStatus: Int? let micNickColor: String? let micCircle: String? let audioCard: AudioCard? let userInfoSkillVo: UserInfoSkillVo? let userInfoCardPic: String? let iosBubbleUrl: String? let androidBubbleUrl: String? let status: String? // 兼容旧字段 let username: String? // 兼容旧字段 let email: String? // 兼容旧字段 let phone: String? // 兼容旧字段 let updateTime: String? // 兼容旧字段 enum CodingKeys: String, CodingKey { case uid case userId = "user_id" case nick case nickname case avatar case region case regionDesc case gender case birth case userDesc case userLevelVo case userVipInfoVO case medalsPic case userHeadwear case privatePhoto case createTime case phoneAreaCode case erbanNo case isCertified case isBindPhone case isBindApple case isBindPasswd case isBindPaymentPwd case banAccount case visitNum case fansNum case followNum case visitHide case visitListView case newUser case defUser case platformRole case bindType case showLimitCharge case uploadGifAvatarPrice case hasRegPacket case hasPrettyErbanNo case hasSuperRole case isRechargeUser case isFirstCharge case fromSayHelloChannel case partitionId case useStatus case micNickColor case micCircle case audioCard case userInfoSkillVo case userInfoCardPic case iosBubbleUrl case androidBubbleUrl case status case username case email case phone case updateTime } } // MARK: - 嵌套对象结构体 struct UserLevelVo: Codable, Equatable { let experUrl: String? let charmLevelSeq: Int? let experLevelName: String? let charmLevelName: String? let charmAmount: Int? let experLevelGrp: String? let charmUrl: String? let experLevelSeq: Int? let experAmount: Int? let charmLevelGrp: String? } struct UserVipInfoVO: Codable, Equatable { let vipIcon: String? let nameplateId: Int? let vipLogo: String? let userCardBG: String? let preventKick: Bool? let preventTrace: Bool? let preventFollow: Bool? let micNickColour: String? let micCircle: String? let enterRoomEffects: String? let medalSeat: Int? let friendNickColour: String? let visitHide: Bool? let visitListView: Bool? let privateChatLimit: Bool? let nameplateUrl: String? let roomPicScreen: Bool? let uploadGifAvatar: Bool? let expireTime: Int64? let enterHide: Bool? let vipLevel: Int? let vipName: String? } struct MedalsPic: Codable, Equatable { let picUrl: String? let mp4Url: String? } struct UserHeadwear: Codable, Equatable { let expireTime: Int64? let renewPrice: Int? let uid: Int? let comeFrom: Int? let labelType: Int? let limitDesc: String? let redirectLink: String? let headwearId: Int? let buyTime: Int64? let pic: String? let used: Bool? let price: Int? let originalPrice: Int? let type: Int? let days: Int? let headwearName: String? let effect: String? let expireDays: Int? let status: Int? } struct PrivatePhoto: Codable, Equatable { let seqNo: Int? let photoUrl: String? let createTime: Int64? let review: Bool? let pid: Int? } struct AudioCard: Codable, Equatable { let uid: Int? let status: Int? } struct UserInfoSkillVo: Codable, Equatable { let liveTag: Bool? let liveSkillVoList: [LiveSkillVo]? } struct LiveSkillVo: Codable, Equatable { // 具体字段根据API返回补充,这里暂留空 } // MARK: - Login Helper struct LoginHelper { /// 创建ID登录请求 /// 这个方法会自动处理DES加密 /// - Parameters: /// - userID: 原始用户ID /// - password: 原始密码 /// - Returns: 配置好的API请求,如果加密失败返回nil static func createIDLoginRequest(userID: String, password: String) async -> IDLoginAPIRequest? { // 使用DES加密ID和密码 let encryptionKey = "1ea53d260ecf11e7b56e00163e046a26" guard let encryptedID = DESEncrypt.encryptUseDES(userID, key: encryptionKey), let encryptedPassword = DESEncrypt.encryptUseDES(password, key: encryptionKey) else { debugErrorSync("❌ DES加密失败") return nil } debugInfoSync("🔐 DES加密成功") debugInfoSync(" 原始ID: \(userID)") debugInfoSync(" 加密后ID: \(encryptedID)") debugInfoSync(" 原始密码: \(password)") debugInfoSync(" 加密后密码: \(encryptedPassword)") return IDLoginAPIRequest( phone: encryptedID, password: encryptedPassword ) } } // MARK: - Ticket API Models /// Ticket 请求结构体 struct TicketAPIRequest: APIRequestProtocol { typealias Response = TicketResponse let endpoint = "/oauth/ticket" let method: HTTPMethod = .POST let includeBaseParameters = true let queryParameters: [String: String]? var bodyParameters: [String: Any]? { nil } let timeout: TimeInterval = 30.0 let customHeaders: [String: String]? /// 初始化 Ticket 请求 /// - Parameters: /// - accessToken: OAuth 访问令牌 /// - issueType: 签发类型,固定为"multi" /// - uid: 用户唯一标识,用于添加到请求头 init(accessToken: String, issueType: String = "multi", uid: Int? = nil) { self.queryParameters = [ "access_token": accessToken, "issue_type": issueType ] // 设置自定义请求头 var headers: [String: String] = [:] if let uid = uid { headers["pub_uid"] = "\(uid)" // 转换为字符串 } self.customHeaders = headers.isEmpty ? nil : headers } } /// Ticket 响应结构体 struct TicketResponse: Codable, Equatable { let code: Int? let message: String? let data: TicketData? /// 是否获取成功 var isSuccess: Bool { return code == 200 } /// 错误消息(如果有) var errorMessage: String { return message ?? "Ticket 获取失败,请重试" } /// 获取 Ticket 字符串 var ticket: String? { return data?.tickets?.first?.ticket } } /// Ticket 数据结构体 struct TicketData: Codable, Equatable { let tickets: [TicketInfo]? } /// Ticket 信息结构体 struct TicketInfo: Codable, Equatable { let ticket: String? } // MARK: - Ticket Helper struct TicketHelper { /// 创建 Ticket 请求 /// - Parameters: /// - accessToken: OAuth 访问令牌 /// - uid: 用户唯一标识 /// - Returns: 配置好的 Ticket API 请求 static func createTicketRequest(accessToken: String, uid: Int?) -> TicketAPIRequest { return TicketAPIRequest(accessToken: accessToken, uid: uid) } /// 调试打印 Ticket 请求信息 /// - Parameters: /// - accessToken: OAuth 访问令牌 /// - uid: 用户唯一标识 static func debugTicketRequest(accessToken: String, uid: Int?) { debugInfoSync("🎫 Ticket 请求调试信息") debugInfoSync(" AccessToken: \(accessToken)") debugInfoSync(" UID: \(uid?.description ?? "nil")") debugInfoSync(" Endpoint: /oauth/ticket") debugInfoSync(" Method: POST") debugInfoSync(" Headers: pub_uid = \(uid?.description ?? "nil")") debugInfoSync(" Parameters: access_token=\(accessToken), issue_type=multi") } } // MARK: - 兼容旧的LoginResponse(如果需要) typealias LoginResponse = IDLoginResponse // MARK: - Email Verification Code Models /// 邮箱验证码获取请求 struct EmailGetCodeRequest: APIRequestProtocol { typealias Response = EmailGetCodeResponse let endpoint = APIEndpoint.emailGetCode.path let method: HTTPMethod = .POST let includeBaseParameters = true let queryParameters: [String: String]? var bodyParameters: [String: Any]? { nil } let timeout: TimeInterval = 30.0 /// 初始化邮箱验证码获取请求 /// - Parameters: /// - emailAddress: DES加密后的邮箱地址 /// - type: 验证码类型(1=注册/登录) init(emailAddress: String, type: Int = 1) { self.queryParameters = [ "emailAddress": emailAddress, "type": String(type) ] } } /// 邮箱验证码获取响应 struct EmailGetCodeResponse: 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 ?? "验证码发送失败,请重试" } } /// 邮箱验证码登录请求 struct EmailLoginRequest: APIRequestProtocol { typealias Response = IDLoginResponse // 复用ID登录的响应模型 let endpoint = APIEndpoint.login.path let method: HTTPMethod = .POST let includeBaseParameters = true var bodyParameters: [String: Any]? { nil } let timeout: TimeInterval = 30.0 // MARK: - Private Properties private let email: String private let code: String private let clientSecret: String private let version: String private let clientId: String private let grantType: String // MARK: - Computed Properties var queryParameters: [String: String]? { return [ "email": email, "code": code, "client_secret": clientSecret, "version": version, "client_id": clientId, "grant_type": grantType ] } /// 初始化邮箱验证码登录请求 /// - Parameters: /// - email: DES加密后的邮箱地址 /// - code: 验证码 /// - clientSecret: 客户端密钥,固定为"uyzjdhds" /// - version: 版本号,固定为"1" /// - clientId: 客户端ID,固定为"erban-client" /// - grantType: 授权类型,固定为"email" init(email: String, code: String, clientSecret: String = "uyzjdhds", version: String = "1", clientId: String = "erban-client", grantType: String = "email") { self.email = email self.code = code self.clientSecret = clientSecret self.version = version self.clientId = clientId self.grantType = grantType } } // MARK: - Email Login Helper extension LoginHelper { /// 创建邮箱验证码获取请求 /// - Parameter email: 原始邮箱地址 /// - Returns: 配置好的API请求,如果加密失败返回nil static func createEmailGetCodeRequest(email: String) -> EmailGetCodeRequest? { let encryptionKey = "1ea53d260ecf11e7b56e00163e046a26" guard let encryptedEmail = DESEncrypt.encryptUseDES(email, key: encryptionKey) else { debugErrorSync("❌ 邮箱DES加密失败") return nil } debugInfoSync("🔐 邮箱DES加密成功") debugInfoSync(" 原始邮箱: \(email)") debugInfoSync(" 加密邮箱: \(encryptedEmail)") return EmailGetCodeRequest(emailAddress: email, type: 1) } /// 创建邮箱验证码登录请求 /// - Parameters: /// - email: 原始邮箱地址 /// - code: 验证码 /// - Returns: 配置好的API请求,如果加密失败返回nil static func createEmailLoginRequest(email: String, code: String) async -> EmailLoginRequest? { let encryptionKey = "1ea53d260ecf11e7b56e00163e046a26" guard let encryptedEmail = DESEncrypt.encryptUseDES(email, key: encryptionKey) else { debugErrorSync("❌ 邮箱DES加密失败") return nil } debugInfoSync("🔐 邮箱验证码登录DES加密成功") debugInfoSync(" 原始邮箱: \(email)") debugInfoSync(" 加密邮箱: \(encryptedEmail)") debugInfoSync(" 验证码: \(code)") return EmailLoginRequest(email: encryptedEmail, code: code) } } // MARK: - User Info API Models /// 获取用户信息请求模型 struct GetUserInfoRequest: APIRequestProtocol { typealias Response = GetUserInfoResponse let endpoint = APIEndpoint.getUserInfo.path let method: HTTPMethod = .GET let includeBaseParameters = true var bodyParameters: [String: Any]? { nil } let timeout: TimeInterval = 30.0 let shouldShowLoading: Bool = false // 不显示loading,避免影响用户体验 let shouldShowError: Bool = false // 不显示错误,静默处理 // MARK: - Private Properties private let uid: String // MARK: - Computed Properties var queryParameters: [String: String]? { return [ "uid": uid ] } /// 初始化获取用户信息请求 /// - Parameter uid: 要查询的用户ID init(uid: String) { self.uid = uid } } /// 获取用户信息响应模型 struct GetUserInfoResponse: Codable, Equatable { let code: Int? let message: String? let timestamp: Int64? let data: UserInfo? /// 是否获取成功 var isSuccess: Bool { return code == 200 } /// 错误消息(如果有) var errorMessage: String { return message ?? "获取用户信息失败,请重试" } } // MARK: - User Info Helper struct UserInfoHelper { /// 创建获取用户信息请求 /// - Parameter uid: 用户ID /// - Returns: 配置好的API请求 static func createGetUserInfoRequest(uid: String) -> GetUserInfoRequest { return GetUserInfoRequest(uid: uid) } /// 调试打印获取用户信息请求 /// - Parameter uid: 用户ID static func debugGetUserInfoRequest(uid: String) { debugInfoSync("👤 获取用户信息请求调试") debugInfoSync(" UID: \(uid)") debugInfoSync(" Endpoint: /user/get") debugInfoSync(" Method: GET") debugInfoSync(" Parameters: uid=\(uid)") } }