feat: 更新Podfile和Podfile.lock,移除Alamofire依赖并添加API认证机制文档

- 注释掉Podfile中的Alamofire依赖,更新Podfile.lock以反映更改。
- 在yana/APIs/API-README.md中新增自动认证Header机制的详细文档,描述其工作原理、实现细节及最佳实践。
- 在yana/yanaApp.swift中将print语句替换为debugInfo以增强调试信息的输出。
- 在API相关文件中实现用户认证状态检查和相关header的自动添加逻辑,提升API请求的安全性和用户体验。
- 更新多个文件中的日志输出,确保在DEBUG模式下提供详细的调试信息。
This commit is contained in:
edwinQQQ
2025-07-11 16:53:46 +08:00
parent 750eecf6ff
commit f9f3dec53f
30 changed files with 537 additions and 219 deletions

152
yana/APIs/API-README.md Normal file
View File

@@ -0,0 +1,152 @@
## 🔐 **自动认证 Header 机制**
### 概述
系统会自动检查用户的登录状态并在所有API请求中自动添加认证相关的header。
### 工作原理
1. **检查认证状态**每次发起API请求时系统会检查`AccountModel`的有效性
2. **自动添加Header**如果用户已登录且认证信息有效自动添加以下header
- `pub_uid`: 用户唯一标识(来自`AccountModel.uid`
- `pub_ticket`: 业务会话票据(来自`AccountModel.ticket`
### 实现细节
```swift
// 在 APIConfiguration.defaultHeaders 中实现
static var defaultHeaders: [String: String] {
var headers = [
"Content-Type": "application/json",
"Accept": "application/json",
// ... 其他基础header
]
// 检查用户认证状态并添加相关 headers
let authStatus = UserInfoManager.checkAuthenticationStatus()
if authStatus.canAutoLogin {
// 添加认证 headers仅在 AccountModel 有效时)
if let userId = UserInfoManager.getCurrentUserId() {
headers["pub_uid"] = userId
}
if let userTicket = UserInfoManager.getCurrentUserTicket() {
headers["pub_ticket"] = userTicket
}
}
return headers
}
```
### 认证状态检查
系统使用`UserInfoManager.checkAuthenticationStatus()`检查认证状态:
```swift
enum AuthenticationStatus {
case valid // 认证有效,可以自动登录
case invalid // 认证信息不完整或无效
case notFound // 未找到认证信息
}
```
**认证有效的条件**
- `AccountModel`存在
- `uid`不为空
- `ticket`不为空
- `accessToken`不为空
### 使用方式
认证header的添加是**完全自动的**,开发者无需手动处理:
```swift
// 示例发起API请求
let request = ConfigRequest()
let response = try await apiService.request(request)
// 如果用户已登录,以上请求会自动包含:
// Header: pub_uid = "12345"
// Header: pub_ticket = "eyJhbGciOiJIUzI1NiJ9..."
```
### 测试功能
在DEBUG模式下可以使用测试方法验证功能
```swift
#if DEBUG
// 运行认证header测试
UserInfoManager.testAuthenticationHeaders()
#endif
```
测试包括:
1. **未登录状态测试**验证不会添加认证header
2. **已登录状态测试**验证正确添加认证header
3. **清理测试**:验证测试数据正确清理
### 调试日志
在DEBUG模式下系统会输出认证header的添加情况
```
🔐 添加认证 header: pub_uid = 12345
🔐 添加认证 header: pub_ticket = eyJhbGciOiJIUzI1NiJ9...
```
或者:
```
🔐 跳过认证 header 添加 - 认证状态: 未找到认证信息
```
### 最佳实践
1. **登录成功后保存完整认证信息**
```swift
UserInfoManager.saveCompleteAuthenticationData(
accessToken: loginResponse.accessToken,
ticket: ticketResponse.ticket,
uid: loginResponse.uid,
userInfo: loginResponse.userInfo
)
```
2. **登出时清理认证信息**
```swift
UserInfoManager.clearAllAuthenticationData()
```
3. **应用启动时检查认证状态**
```swift
let authStatus = UserInfoManager.checkAuthenticationStatus()
if authStatus.canAutoLogin {
// 可以直接进入主界面
} else {
// 需要重新登录
}
```
### 安全考虑
- **内存安全**ticket存储在内存中应用重启需重新获取
- **持久化安全**uid和accessToken存储在Keychain中确保安全性
- **自动清理**认证失效时系统会自动停止添加认证header
### 故障排除
1. **认证header未添加**
- 检查用户是否已正确登录
- 验证AccountModel是否包含有效的uid和ticket
- 确认认证状态为valid
2. **ticket为空**
- 检查登录流程是否正确获取了ticket
- 验证ticket是否正确保存到AccountModel
3. **调试模式下查看详细日志**
- 启用DEBUG模式查看认证header添加日志
- 使用测试方法验证功能正确性

View File

@@ -94,13 +94,28 @@ struct APIConfiguration {
"User-Agent": "YuMi/20.20.61 (iPhone; iOS 16.4; Scale/2.00)"
]
// headers
if let userId = UserInfoManager.getCurrentUserId() {
headers["pub_uid"] = userId
}
// headers
let authStatus = UserInfoManager.checkAuthenticationStatus()
if let userTicket = UserInfoManager.getCurrentUserTicket() {
headers["pub_ticket"] = userTicket
if authStatus.canAutoLogin {
// headers AccountModel
if let userId = UserInfoManager.getCurrentUserId() {
headers["pub_uid"] = userId
#if DEBUG
debugInfo("🔐 添加认证 header: pub_uid = \(userId)")
#endif
}
if let userTicket = UserInfoManager.getCurrentUserTicket() {
headers["pub_ticket"] = userTicket
#if DEBUG
debugInfo("🔐 添加认证 header: pub_ticket = \(userTicket.prefix(20))...")
#endif
}
} else {
#if DEBUG
debugInfo("🔐 跳过认证 header 添加 - 认证状态: \(authStatus.description)")
#endif
}
return headers

View File

@@ -22,7 +22,11 @@ class APILogger {
// MARK: - Request Logging
static func logRequest<T: APIRequestProtocol>(_ request: T, url: URL, body: Data?, finalHeaders: [String: String]? = nil) {
#if DEBUG
guard logLevel != .none else { return }
#else
return
#endif
let timestamp = dateFormatter.string(from: Date())
@@ -107,7 +111,11 @@ class APILogger {
// MARK: - Response Logging
static func logResponse(data: Data, response: HTTPURLResponse, duration: TimeInterval) {
#if DEBUG
guard logLevel != .none else { return }
#else
return
#endif
let timestamp = dateFormatter.string(from: Date())
let statusEmoji = response.statusCode < 400 ? "" : ""
@@ -143,7 +151,11 @@ class APILogger {
// MARK: - Error Logging
static func logError(_ error: Error, url: URL?, duration: TimeInterval) {
#if DEBUG
guard logLevel != .none else { return }
#else
return
#endif
let timestamp = dateFormatter.string(from: Date())
@@ -186,7 +198,11 @@ class APILogger {
// MARK: - Decoded Response Logging
static func logDecodedResponse<T>(_ response: T, type: T.Type) {
#if DEBUG
guard logLevel == .detailed else { return }
#else
return
#endif
let timestamp = dateFormatter.string(from: Date())
print("🎯 [Decoded Response] [\(timestamp)] Type: \(type)")
@@ -203,7 +219,11 @@ class APILogger {
// MARK: - Performance Logging
static func logPerformanceWarning(duration: TimeInterval, threshold: TimeInterval = 5.0) {
#if DEBUG
guard logLevel != .none && duration > threshold else { return }
#else
return
#endif
let timestamp = dateFormatter.string(from: Date())
print("\n⚠️ [Performance Warning] [\(timestamp)] ============")

View File

@@ -260,21 +260,27 @@ struct UserInfoManager {
return getAccountModel()?.accessToken
}
// MARK: - Ticket Management ()
// MARK: - Ticket Management ( AccountModel )
private static var currentTicket: String?
static func getCurrentUserTicket() -> String? {
// AccountModel ticket
if let accountTicket = getAccountModel()?.ticket, !accountTicket.isEmpty {
return accountTicket
}
//
return currentTicket
}
static func saveTicket(_ ticket: String) {
currentTicket = ticket
print("💾 保存 Ticket 到内存")
debugInfo("💾 保存 Ticket 到内存")
}
static func clearTicket() {
currentTicket = nil
print("🗑️ 清除 Ticket")
debugInfo("🗑️ 清除 Ticket")
}
// MARK: - User Info Management
@@ -283,9 +289,9 @@ struct UserInfoManager {
do {
try keychain.store(userInfo, forKey: StorageKeys.userInfo)
userInfoCache = userInfo
print("💾 保存用户信息成功")
debugInfo("💾 保存用户信息成功")
} catch {
print("❌ 保存用户信息失败: \(error)")
debugError("❌ 保存用户信息失败: \(error)")
}
}
}
@@ -303,7 +309,7 @@ struct UserInfoManager {
userInfoCache = userInfo
return userInfo
} catch {
print("❌ 读取用户信息失败: \(error)")
debugError("❌ 读取用户信息失败: \(error)")
return nil
}
}
@@ -337,7 +343,7 @@ struct UserInfoManager {
saveUserInfo(userInfo)
}
print("✅ 完整认证信息保存成功")
debugInfo("✅ 完整认证信息保存成功")
}
///
@@ -351,7 +357,7 @@ struct UserInfoManager {
clearUserInfo()
clearTicket()
print("🗑️ 清除所有认证信息")
debugInfo("🗑️ 清除所有认证信息")
}
/// Ticket
@@ -361,7 +367,7 @@ struct UserInfoManager {
return false
}
print("🔄 尝试使用 Access Token 恢复 Ticket...")
debugInfo("🔄 尝试使用 Access Token 恢复 Ticket...")
// APIService false
// TicketHelper.createTicketRequest
@@ -382,9 +388,9 @@ struct UserInfoManager {
saveTicket(ticket)
}
print("💾 AccountModel 保存成功")
debugInfo("💾 AccountModel 保存成功")
} catch {
print("❌ AccountModel 保存失败: \(error)")
debugError("❌ AccountModel 保存失败: \(error)")
}
}
}
@@ -404,7 +410,7 @@ struct UserInfoManager {
accountModelCache = accountModel
return accountModel
} catch {
print("❌ 读取 AccountModel 失败: \(error)")
debugError("❌ 读取 AccountModel 失败: \(error)")
return nil
}
}
@@ -414,7 +420,7 @@ struct UserInfoManager {
/// - Parameter ticket:
static func updateAccountModelTicket(_ ticket: String) {
guard var accountModel = getAccountModel() else {
print("❌ 无法更新 ticketAccountModel 不存在")
debugError("❌ 无法更新 ticketAccountModel 不存在")
return
}
@@ -449,9 +455,9 @@ struct UserInfoManager {
do {
try keychain.delete(forKey: StorageKeys.accountModel)
accountModelCache = nil
print("🗑️ AccountModel 已清除")
debugInfo("🗑️ AccountModel 已清除")
} catch {
print("❌ 清除 AccountModel 失败: \(error)")
debugError("❌ 清除 AccountModel 失败: \(error)")
}
}
}
@@ -462,9 +468,9 @@ struct UserInfoManager {
do {
try keychain.delete(forKey: StorageKeys.userInfo)
userInfoCache = nil
print("🗑️ UserInfo 已清除")
debugInfo("🗑️ UserInfo 已清除")
} catch {
print("❌ 清除 UserInfo 失败: \(error)")
debugError("❌ 清除 UserInfo 失败: \(error)")
}
}
}
@@ -474,7 +480,7 @@ struct UserInfoManager {
cacheQueue.async(flags: .barrier) {
accountModelCache = nil
userInfoCache = nil
print("🗑️ 清除所有内存缓存")
debugInfo("🗑️ 清除所有内存缓存")
}
}
@@ -485,7 +491,7 @@ struct UserInfoManager {
_ = getAccountModel()
// UserInfo
_ = getUserInfo()
print("🚀 缓存预加载完成")
debugInfo("🚀 缓存预加载完成")
}
}
@@ -496,29 +502,29 @@ struct UserInfoManager {
static func checkAuthenticationStatus() -> AuthenticationStatus {
return cacheQueue.sync {
guard let accountModel = getAccountModel() else {
print("🔍 认证检查:未找到 AccountModel")
debugInfo("🔍 认证检查:未找到 AccountModel")
return .notFound
}
// uid
guard let uid = accountModel.uid, !uid.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
print("🔍 认证检查uid 无效")
debugInfo("🔍 认证检查uid 无效")
return .invalid
}
// ticket
guard let ticket = accountModel.ticket, !ticket.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
print("🔍 认证检查ticket 无效")
debugInfo("🔍 认证检查ticket 无效")
return .invalid
}
// access token
guard let accessToken = accountModel.accessToken, !accessToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
print("🔍 认证检查access token 无效")
debugInfo("🔍 认证检查access token 无效")
return .invalid
}
print("🔍 认证检查:认证有效 - uid: \(uid), ticket: \(ticket.prefix(10))...")
debugInfo("🔍 认证检查:认证有效 - uid: \(uid), ticket: \(ticket.prefix(10))...")
return .valid
}
}
@@ -545,6 +551,49 @@ struct UserInfoManager {
return self == .valid
}
}
// MARK: - Testing and Debugging
/// header
/// header
static func testAuthenticationHeaders() {
#if DEBUG
debugInfo("\n🧪 开始测试认证 header 功能")
// 1
debugInfo("📝 测试1未登录状态")
clearAllAuthenticationData()
let headers1 = APIConfiguration.defaultHeaders
let hasAuthHeaders1 = headers1.keys.contains("pub_uid") || headers1.keys.contains("pub_ticket")
debugInfo(" 认证 headers 存在: \(hasAuthHeaders1) (应该为 false)")
// 2
debugInfo("📝 测试2模拟登录状态")
let testAccount = AccountModel(
uid: "12345",
jti: "test-jti",
tokenType: "bearer",
refreshToken: nil,
netEaseToken: nil,
accessToken: "test-access-token",
expiresIn: 3600,
scope: "read write",
ticket: "test-ticket-12345678901234567890"
)
saveAccountModel(testAccount)
let headers2 = APIConfiguration.defaultHeaders
let hasUid = headers2["pub_uid"] == "12345"
let hasTicket = headers2["pub_ticket"] == "test-ticket-12345678901234567890"
debugInfo(" pub_uid 正确: \(hasUid) (应该为 true)")
debugInfo(" pub_ticket 正确: \(hasTicket) (应该为 true)")
// 3
debugInfo("📝 测试3清理测试数据")
clearAllAuthenticationData()
debugInfo("✅ 认证 header 测试完成\n")
#endif
}
}
// MARK: - API Request Protocol

View File

@@ -93,6 +93,7 @@ struct LiveAPIService: APIServiceProtocol {
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = request.method.rawValue
urlRequest.timeoutInterval = request.timeout
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
//
var headers = APIConfiguration.defaultHeaders
@@ -113,18 +114,30 @@ struct LiveAPIService: APIServiceProtocol {
var requestBody: Data? = nil
if request.method != .GET, let bodyParams = request.bodyParameters {
do {
//
var finalBody = bodyParams
//
if request.includeBaseParameters {
//
var baseParams = BaseRequest()
// API rule
// bodyParams +
baseParams.generateSignature(with: bodyParams)
//
let baseDict = try baseParams.toDictionary()
finalBody.merge(baseDict) { existing, _ in existing }
finalBody.merge(baseDict) { _, new in new } //
debugInfo("🔐 签名生成完成 - 基于所有参数统一生成: \(baseParams.pubSign)")
}
requestBody = try JSONSerialization.data(withJSONObject: finalBody, options: [])
urlRequest.httpBody = requestBody
// urlRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
if let httpBody = urlRequest.httpBody,
let bodyString = String(data: httpBody, encoding: .utf8) {
debugInfo("HTTP Body: \(bodyString)")
}
} catch {
let encodingError = APIError.decodingError("请求体编码失败: \(error.localizedDescription)")
APILoadingManager.shared.setError(loadingId, errorMessage: encodingError.localizedDescription)
@@ -133,7 +146,7 @@ struct LiveAPIService: APIServiceProtocol {
}
// headers
// APILogger.logRequest(request, url: url, body: requestBody, finalHeaders: headers)
APILogger.logRequest(request, url: url, body: requestBody, finalHeaders: headers)
do {
//
@@ -226,16 +239,22 @@ struct LiveAPIService: APIServiceProtocol {
// GET
if request.method == .GET && request.includeBaseParameters {
do {
//
var baseParams = BaseRequest()
// GET
// queryParams +
let queryParamsDict = request.queryParameters ?? [:]
baseParams.generateSignature(with: queryParamsDict)
//
let baseDict = try baseParams.toDictionary()
for (key, value) in baseDict {
queryItems.append(URLQueryItem(name: key, value: "\(value)"))
}
debugInfo("🔐 GET请求签名生成完成 - 基于所有参数统一生成: \(baseParams.pubSign)")
} catch {
print("警告:无法添加基础参数到查询字符串")
debugWarn("警告:无法添加基础参数到查询字符串")
}
}

View File

@@ -105,7 +105,7 @@ struct IDLoginAPIRequest: APIRequestProtocol {
// "version": version,
// "client_id": clientId,
// "grant_type": grantType
// ]
// ];
}
}
@@ -192,15 +192,15 @@ struct LoginHelper {
guard let encryptedID = DESEncrypt.encryptUseDES(userID, key: encryptionKey),
let encryptedPassword = DESEncrypt.encryptUseDES(password, key: encryptionKey) else {
print("❌ DES加密失败")
debugError("❌ DES加密失败")
return nil
}
print("🔐 DES加密成功")
print(" 原始ID: \(userID)")
print(" 加密后ID: \(encryptedID)")
print(" 原始密码: \(password)")
print(" 加密后密码: \(encryptedPassword)")
debugInfo("🔐 DES加密成功")
debugInfo(" 原始ID: \(userID)")
debugInfo(" 加密后ID: \(encryptedID)")
debugInfo(" 原始密码: \(password)")
debugInfo(" 加密后密码: \(encryptedPassword)")
return IDLoginAPIRequest(
phone: userID,
@@ -292,13 +292,13 @@ struct TicketHelper {
/// - accessToken: OAuth 访
/// - uid:
static func debugTicketRequest(accessToken: String, uid: Int?) {
print("🎫 Ticket 请求调试信息")
print(" AccessToken: \(accessToken)")
print(" UID: \(uid?.description ?? "nil")")
print(" Endpoint: /oauth/ticket")
print(" Method: POST")
print(" Headers: pub_uid = \(uid?.description ?? "nil")")
print(" Parameters: access_token=\(accessToken), issue_type=multi")
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")
}
}
@@ -389,13 +389,13 @@ extension LoginHelper {
let encryptionKey = "1ea53d260ecf11e7b56e00163e046a26"
guard let encryptedEmail = DESEncrypt.encryptUseDES(email, key: encryptionKey) else {
print("❌ 邮箱DES加密失败")
debugError("❌ 邮箱DES加密失败")
return nil
}
print("🔐 邮箱DES加密成功")
print(" 原始邮箱: \(email)")
print(" 加密邮箱: \(encryptedEmail)")
debugInfo("🔐 邮箱DES加密成功")
debugInfo(" 原始邮箱: \(email)")
debugInfo(" 加密邮箱: \(encryptedEmail)")
return EmailGetCodeRequest(emailAddress: email, type: 1)
}
@@ -409,14 +409,14 @@ extension LoginHelper {
let encryptionKey = "1ea53d260ecf11e7b56e00163e046a26"
guard let encryptedEmail = DESEncrypt.encryptUseDES(email, key: encryptionKey) else {
print("❌ 邮箱DES加密失败")
debugError("❌ 邮箱DES加密失败")
return nil
}
print("🔐 邮箱验证码登录DES加密成功")
print(" 原始邮箱: \(email)")
print(" 加密邮箱: \(encryptedEmail)")
print(" 验证码: \(code)")
debugInfo("🔐 邮箱验证码登录DES加密成功")
debugInfo(" 原始邮箱: \(email)")
debugInfo(" 加密邮箱: \(encryptedEmail)")
debugInfo(" 验证码: \(code)")
return EmailLoginRequest(email: encryptedEmail, code: code)
}