
- 注释掉Podfile中的Alamofire依赖,更新Podfile.lock以反映更改。 - 在yana/APIs/API-README.md中新增自动认证Header机制的详细文档,描述其工作原理、实现细节及最佳实践。 - 在yana/yanaApp.swift中将print语句替换为debugInfo以增强调试信息的输出。 - 在API相关文件中实现用户认证状态检查和相关header的自动添加逻辑,提升API请求的安全性和用户体验。 - 更新多个文件中的日志输出,确保在DEBUG模式下提供详细的调试信息。
424 lines
14 KiB
Swift
424 lines
14 KiB
Swift
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
|
||
let queryParameters: [String: String]?
|
||
let bodyParameters: [String: Any]? = nil
|
||
let timeout: TimeInterval = 30.0
|
||
|
||
/// 初始化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.queryParameters = [
|
||
"phone": phone,
|
||
"password": password,
|
||
"client_secret": clientSecret,
|
||
"version": version,
|
||
"client_id": clientId,
|
||
"grant_type": grantType
|
||
];
|
||
// self.bodyParameters = [
|
||
// "phone": phone,
|
||
// "password": password,
|
||
// "client_secret": clientSecret,
|
||
// "version": version,
|
||
// "client_id": clientId,
|
||
// "grant_type": 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 userId: String?
|
||
let username: String?
|
||
let nickname: String?
|
||
let avatar: String?
|
||
let email: String?
|
||
let phone: String?
|
||
let status: String?
|
||
let createTime: String?
|
||
let updateTime: String?
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case userId = "user_id"
|
||
case username
|
||
case nickname
|
||
case avatar
|
||
case email
|
||
case phone
|
||
case status
|
||
case createTime = "create_time"
|
||
case updateTime = "update_time"
|
||
}
|
||
}
|
||
|
||
// MARK: - Login Helper
|
||
struct LoginHelper {
|
||
|
||
/// 创建ID登录请求
|
||
/// 这个方法会自动处理DES加密
|
||
/// - Parameters:
|
||
/// - userID: 原始用户ID
|
||
/// - password: 原始密码
|
||
/// - Returns: 配置好的API请求,如果加密失败返回nil
|
||
static func createIDLoginRequest(userID: String, password: String) -> IDLoginAPIRequest? {
|
||
// 使用DES加密ID和密码
|
||
let encryptionKey = "1ea53d260ecf11e7b56e00163e046a26"
|
||
|
||
guard let encryptedID = DESEncrypt.encryptUseDES(userID, key: encryptionKey),
|
||
let encryptedPassword = DESEncrypt.encryptUseDES(password, key: encryptionKey) else {
|
||
debugError("❌ DES加密失败")
|
||
return nil
|
||
}
|
||
|
||
debugInfo("🔐 DES加密成功")
|
||
debugInfo(" 原始ID: \(userID)")
|
||
debugInfo(" 加密后ID: \(encryptedID)")
|
||
debugInfo(" 原始密码: \(password)")
|
||
debugInfo(" 加密后密码: \(encryptedPassword)")
|
||
|
||
return IDLoginAPIRequest(
|
||
phone: userID,
|
||
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]?
|
||
let 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?) {
|
||
debugInfo("🎫 Ticket 请求调试信息")
|
||
debugInfo(" AccessToken: \(accessToken)")
|
||
debugInfo(" UID: \(uid?.description ?? "nil")")
|
||
debugInfo(" Endpoint: /oauth/ticket")
|
||
debugInfo(" Method: POST")
|
||
debugInfo(" Headers: pub_uid = \(uid?.description ?? "nil")")
|
||
debugInfo(" 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]?
|
||
let 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
|
||
let queryParameters: [String: String]?
|
||
let bodyParameters: [String: Any]? = nil
|
||
let timeout: TimeInterval = 30.0
|
||
|
||
/// 初始化邮箱验证码登录请求
|
||
/// - 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.queryParameters = [
|
||
"email": email,
|
||
"code": code,
|
||
"client_secret": clientSecret,
|
||
"version": version,
|
||
"client_id": clientId,
|
||
"grant_type": 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 {
|
||
debugError("❌ 邮箱DES加密失败")
|
||
return nil
|
||
}
|
||
|
||
debugInfo("🔐 邮箱DES加密成功")
|
||
debugInfo(" 原始邮箱: \(email)")
|
||
debugInfo(" 加密邮箱: \(encryptedEmail)")
|
||
|
||
return EmailGetCodeRequest(emailAddress: email, type: 1)
|
||
}
|
||
|
||
/// 创建邮箱验证码登录请求
|
||
/// - Parameters:
|
||
/// - email: 原始邮箱地址
|
||
/// - code: 验证码
|
||
/// - Returns: 配置好的API请求,如果加密失败返回nil
|
||
static func createEmailLoginRequest(email: String, code: String) -> EmailLoginRequest? {
|
||
let encryptionKey = "1ea53d260ecf11e7b56e00163e046a26"
|
||
|
||
guard let encryptedEmail = DESEncrypt.encryptUseDES(email, key: encryptionKey) else {
|
||
debugError("❌ 邮箱DES加密失败")
|
||
return nil
|
||
}
|
||
|
||
debugInfo("🔐 邮箱验证码登录DES加密成功")
|
||
debugInfo(" 原始邮箱: \(email)")
|
||
debugInfo(" 加密邮箱: \(encryptedEmail)")
|
||
debugInfo(" 验证码: \(code)")
|
||
|
||
return EmailLoginRequest(email: encryptedEmail, code: code)
|
||
}
|
||
}
|