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:
2
Podfile
2
Podfile
@@ -16,7 +16,7 @@ target 'yana' do
|
||||
# pod 'NELocalConversationUIKit', '10.6.1' # 本地会话列表组件。
|
||||
|
||||
# Networks
|
||||
pod 'Alamofire'
|
||||
# pod 'Alamofire'
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
|
15
Podfile.lock
15
Podfile.lock
@@ -1,16 +1,3 @@
|
||||
PODS:
|
||||
- Alamofire (5.10.2)
|
||||
|
||||
DEPENDENCIES:
|
||||
- Alamofire
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- Alamofire
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496
|
||||
|
||||
PODFILE CHECKSUM: 4ccb5fbbedd3dcb71c35d00e7bfd0d280d4ced88
|
||||
PODFILE CHECKSUM: 9817fb04504ebed48143ca78630f70d3b3402405
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
@@ -145,7 +145,6 @@
|
||||
4C3E651B2DB61F7A00E5A455 /* Sources */,
|
||||
4C3E651C2DB61F7A00E5A455 /* Frameworks */,
|
||||
4C3E651D2DB61F7A00E5A455 /* Resources */,
|
||||
A9AAC370C902C50E37521C40 /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -263,23 +262,6 @@
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
A9AAC370C902C50E37521C40 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-yana/Pods-yana-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-yana/Pods-yana-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-yana/Pods-yana-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<key>yana.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>3</integer>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
|
@@ -3,4 +3,38 @@
|
||||
uuid = "A60FAB2A-3184-45B2-920F-A3D7A086CF95"
|
||||
type = "0"
|
||||
version = "2.0">
|
||||
<Breakpoints>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "4D63F38A-4F7C-46D9-8CAF-BCA831664FA0"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "yana/APIs/APIService.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "126"
|
||||
endingLineNumber = "126"
|
||||
landmarkName = "request(_:)"
|
||||
landmarkType = "7">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "19930D63-5B42-4287-8B22-ADF87CAD40E3"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "yana/APIs/APIService.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "112"
|
||||
endingLineNumber = "112"
|
||||
landmarkName = "request(_:)"
|
||||
landmarkType = "7">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
</Breakpoints>
|
||||
</Bucket>
|
||||
|
152
yana/APIs/API-README.md
Normal file
152
yana/APIs/API-README.md
Normal 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添加日志
|
||||
- 使用测试方法验证功能正确性
|
@@ -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
|
||||
|
@@ -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)] ============")
|
||||
|
@@ -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("❌ 无法更新 ticket:AccountModel 不存在")
|
||||
debugError("❌ 无法更新 ticket:AccountModel 不存在")
|
||||
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
|
||||
|
@@ -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("警告:无法添加基础参数到查询字符串")
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -7,12 +7,12 @@ final class ClientConfig {
|
||||
private init() {}
|
||||
|
||||
func initializeClient() {
|
||||
print("✅ 开始初始化客户端 - URL: \(AppConfig.baseURL)/client/init")
|
||||
debugInfo("✅ 开始初始化客户端 - URL: \(AppConfig.baseURL)/client/init")
|
||||
callClientInitAPI() // 调用新方法
|
||||
}
|
||||
|
||||
func callClientInitAPI() {
|
||||
print("🆕 使用GET方法调用初始化接口")
|
||||
debugInfo("🆕 使用GET方法调用初始化接口")
|
||||
|
||||
// let queryParams = [
|
||||
// "debug": "1",
|
||||
|
@@ -110,9 +110,9 @@ struct IDLoginFeature {
|
||||
UserInfoManager.saveUserInfo(userInfo)
|
||||
}
|
||||
|
||||
print("✅ ID 登录 OAuth 认证成功")
|
||||
print("🔑 Access Token: \(accountModel.accessToken ?? "nil")")
|
||||
print("🆔 用户 UID: \(accountModel.uid ?? "nil")")
|
||||
debugInfo("✅ ID 登录 OAuth 认证成功")
|
||||
debugInfo("🔑 Access Token: \(accountModel.accessToken ?? "nil")")
|
||||
debugInfo("🆔 用户 UID: \(accountModel.uid ?? "nil")")
|
||||
|
||||
// 自动获取 ticket
|
||||
return .send(.requestTicket(accessToken: accountModel.accessToken!))
|
||||
@@ -145,7 +145,7 @@ struct IDLoginFeature {
|
||||
let response = try await apiService.request(ticketRequest)
|
||||
await send(.ticketResponse(.success(response)))
|
||||
} catch {
|
||||
print("❌ ID登录 Ticket 获取失败: \(error)")
|
||||
debugError("❌ ID登录 Ticket 获取失败: \(error)")
|
||||
await send(.ticketResponse(.failure(APIError.networkError(error.localizedDescription))))
|
||||
}
|
||||
}
|
||||
@@ -156,8 +156,8 @@ struct IDLoginFeature {
|
||||
state.ticketError = nil
|
||||
state.loginStep = .completed
|
||||
|
||||
print("✅ ID 登录完整流程成功")
|
||||
print("🎫 Ticket 获取成功: \(response.ticket ?? "nil")")
|
||||
debugInfo("✅ ID 登录完整流程成功")
|
||||
debugInfo("🎫 Ticket 获取成功: \(response.ticket ?? "nil")")
|
||||
|
||||
// 更新 AccountModel 中的 ticket 并保存
|
||||
if let ticket = response.ticket {
|
||||
@@ -171,7 +171,7 @@ struct IDLoginFeature {
|
||||
// 发送 Ticket 获取成功通知,触发导航到主页面
|
||||
NotificationCenter.default.post(name: .ticketSuccess, object: nil)
|
||||
} else {
|
||||
print("❌ AccountModel 不存在,无法保存 ticket")
|
||||
debugError("❌ AccountModel 不存在,无法保存 ticket")
|
||||
state.ticketError = "内部错误:账户信息丢失"
|
||||
state.loginStep = .failed
|
||||
}
|
||||
@@ -190,7 +190,7 @@ struct IDLoginFeature {
|
||||
state.isTicketLoading = false
|
||||
state.ticketError = error.localizedDescription
|
||||
state.loginStep = .failed
|
||||
print("❌ ID 登录 Ticket 获取失败: \(error.localizedDescription)")
|
||||
debugError("❌ ID 登录 Ticket 获取失败: \(error.localizedDescription)")
|
||||
return .none
|
||||
|
||||
case .clearTicketError:
|
||||
|
@@ -109,9 +109,9 @@ struct LoginFeature {
|
||||
let accountModel = AccountModel.from(loginData: loginData) {
|
||||
state.accountModel = accountModel
|
||||
|
||||
print("✅ OAuth 认证成功")
|
||||
print("🔑 Access Token: \(accountModel.accessToken ?? "nil")")
|
||||
print("🆔 用户 UID: \(accountModel.uid ?? "nil")")
|
||||
debugInfo("✅ OAuth 认证成功")
|
||||
debugInfo("🔑 Access Token: \(accountModel.accessToken ?? "nil")")
|
||||
debugInfo("🆔 用户 UID: \(accountModel.uid ?? "nil")")
|
||||
|
||||
// 自动获取 ticket
|
||||
return .send(.requestTicket(accessToken: accountModel.accessToken!))
|
||||
@@ -144,7 +144,7 @@ struct LoginFeature {
|
||||
let response = try await apiService.request(ticketRequest)
|
||||
await send(.ticketResponse(.success(response)))
|
||||
} catch {
|
||||
print("❌ Ticket 获取失败: \(error)")
|
||||
debugError("❌ Ticket 获取失败: \(error)")
|
||||
await send(.ticketResponse(.failure(APIError.networkError(error.localizedDescription))))
|
||||
}
|
||||
}
|
||||
@@ -155,8 +155,8 @@ struct LoginFeature {
|
||||
state.ticketError = nil
|
||||
state.loginStep = .completed
|
||||
|
||||
print("✅ 完整登录流程成功")
|
||||
print("🎫 Ticket 获取成功: \(response.ticket ?? "nil")")
|
||||
debugInfo("✅ 完整登录流程成功")
|
||||
debugInfo("🎫 Ticket 获取成功: \(response.ticket ?? "nil")")
|
||||
|
||||
// 更新 AccountModel 中的 ticket 并保存
|
||||
if let ticket = response.ticket {
|
||||
@@ -170,7 +170,7 @@ struct LoginFeature {
|
||||
// 发送 Ticket 获取成功通知,触发导航到主页面
|
||||
NotificationCenter.default.post(name: .ticketSuccess, object: nil)
|
||||
} else {
|
||||
print("❌ AccountModel 不存在,无法保存 ticket")
|
||||
debugError("❌ AccountModel 不存在,无法保存 ticket")
|
||||
state.ticketError = "内部错误:账户信息丢失"
|
||||
state.loginStep = .failed
|
||||
}
|
||||
@@ -189,7 +189,7 @@ struct LoginFeature {
|
||||
state.isTicketLoading = false
|
||||
state.ticketError = error.localizedDescription
|
||||
state.loginStep = .failed
|
||||
print("❌ Ticket 获取失败: \(error.localizedDescription)")
|
||||
debugError("❌ Ticket 获取失败: \(error.localizedDescription)")
|
||||
return .none
|
||||
|
||||
case .clearTicketError:
|
||||
|
@@ -238,13 +238,13 @@ struct RecoverPasswordHelper {
|
||||
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)")
|
||||
|
||||
// 使用type=3表示密码重置验证码
|
||||
return EmailGetCodeRequest(emailAddress: email, type: 3)
|
||||
@@ -261,16 +261,16 @@ struct RecoverPasswordHelper {
|
||||
|
||||
guard let encryptedEmail = DESEncrypt.encryptUseDES(email, key: encryptionKey),
|
||||
let encryptedPassword = DESEncrypt.encryptUseDES(newPassword, key: encryptionKey) else {
|
||||
print("❌ 密码重置DES加密失败")
|
||||
debugError("❌ 密码重置DES加密失败")
|
||||
return nil
|
||||
}
|
||||
|
||||
print("🔐 密码重置DES加密成功")
|
||||
print(" 原始邮箱: \(email)")
|
||||
print(" 加密邮箱: \(encryptedEmail)")
|
||||
print(" 验证码: \(code)")
|
||||
print(" 原始新密码: \(newPassword)")
|
||||
print(" 加密新密码: \(encryptedPassword)")
|
||||
debugInfo("🔐 密码重置DES加密成功")
|
||||
debugInfo(" 原始邮箱: \(email)")
|
||||
debugInfo(" 加密邮箱: \(encryptedEmail)")
|
||||
debugInfo(" 验证码: \(code)")
|
||||
debugInfo(" 原始新密码: \(newPassword)")
|
||||
debugInfo(" 加密新密码: \(encryptedPassword)")
|
||||
|
||||
return ResetPasswordRequest(
|
||||
email: email,
|
||||
|
@@ -55,10 +55,10 @@ struct SplashFeature {
|
||||
|
||||
// 根据认证状态发送相应的导航通知
|
||||
if status.canAutoLogin {
|
||||
print("🎉 自动登录成功,进入主页")
|
||||
debugInfo("🎉 自动登录成功,进入主页")
|
||||
NotificationCenter.default.post(name: .autoLoginSuccess, object: nil)
|
||||
} else {
|
||||
print("🔑 需要手动登录")
|
||||
debugInfo("🔑 需要手动登录")
|
||||
NotificationCenter.default.post(name: .autoLoginFailed, object: nil)
|
||||
}
|
||||
|
||||
|
@@ -18,18 +18,33 @@ public class LogManager {
|
||||
/// - Parameters:
|
||||
/// - level: 日志等级
|
||||
/// - message: 日志内容
|
||||
/// - onlyRelease: 是否仅在 Release 环境输出(默认 false,Debug 全部输出)
|
||||
/// - onlyRelease: 是否仅在 Release 环境输出(已修复逻辑)
|
||||
public func log(_ level: LogLevel, _ message: @autoclosure () -> String, onlyRelease: Bool = false) {
|
||||
#if DEBUG
|
||||
if onlyRelease { return }
|
||||
print("[\(level)] \(message())")
|
||||
// DEBUG 环境:如果 onlyRelease 为 true,则不输出;否则正常输出
|
||||
if !onlyRelease {
|
||||
print("[\(level)] \(message())")
|
||||
}
|
||||
#else
|
||||
// RELEASE 环境:如果 onlyRelease 为 true,则输出;否则不输出
|
||||
if onlyRelease {
|
||||
print("[\(level)] \(message())")
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// 仅在 DEBUG 环境输出的日志(推荐使用)
|
||||
/// - Parameters:
|
||||
/// - level: 日志等级
|
||||
/// - message: 日志内容
|
||||
public func debugLog(_ level: LogLevel, _ message: @autoclosure () -> String) {
|
||||
#if DEBUG
|
||||
print("[\(level)] \(message())")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 快捷方法
|
||||
// MARK: - 原有快捷方法(保持向后兼容)
|
||||
public func logVerbose(_ message: @autoclosure () -> String, onlyRelease: Bool = false) {
|
||||
LogManager.shared.log(.verbose, message(), onlyRelease: onlyRelease)
|
||||
}
|
||||
@@ -48,4 +63,25 @@ public func logWarn(_ message: @autoclosure () -> String, onlyRelease: Bool = fa
|
||||
|
||||
public func logError(_ message: @autoclosure () -> String, onlyRelease: Bool = false) {
|
||||
LogManager.shared.log(.error, message(), onlyRelease: onlyRelease)
|
||||
}
|
||||
|
||||
// MARK: - 新的DEBUG专用快捷方法(推荐使用)
|
||||
public func debugVerbose(_ message: @autoclosure () -> String) {
|
||||
LogManager.shared.debugLog(.verbose, message())
|
||||
}
|
||||
|
||||
public func debugLog(_ message: @autoclosure () -> String) {
|
||||
LogManager.shared.debugLog(.debug, message())
|
||||
}
|
||||
|
||||
public func debugInfo(_ message: @autoclosure () -> String) {
|
||||
LogManager.shared.debugLog(.info, message())
|
||||
}
|
||||
|
||||
public func debugWarn(_ message: @autoclosure () -> String) {
|
||||
LogManager.shared.debugLog(.warn, message())
|
||||
}
|
||||
|
||||
public func debugError(_ message: @autoclosure () -> String) {
|
||||
LogManager.shared.debugLog(.error, message())
|
||||
}
|
@@ -18,24 +18,24 @@ struct APILoadingEffectView: View {
|
||||
if let firstItem = getFirstDisplayItem() {
|
||||
SingleLoadingView(item: firstItem)
|
||||
.onAppear {
|
||||
print("🔍 Loading item appeared: \(firstItem.id)")
|
||||
debugInfo("🔍 Loading item appeared: \(firstItem.id)")
|
||||
}
|
||||
.onDisappear {
|
||||
print("🔍 Loading item disappeared: \(firstItem.id)")
|
||||
debugInfo("🔍 Loading item disappeared: \(firstItem.id)")
|
||||
}
|
||||
}
|
||||
}
|
||||
.allowsHitTesting(false) // 不阻挡用户点击
|
||||
.ignoresSafeArea(.all) // 覆盖整个屏幕
|
||||
.onReceive(loadingManager.$loadingItems) { items in
|
||||
print("🔍 Loading items updated: \(items.count) items")
|
||||
debugInfo("🔍 Loading items updated: \(items.count) items")
|
||||
}
|
||||
}
|
||||
|
||||
/// 安全地获取第一个需要显示的项目
|
||||
private func getFirstDisplayItem() -> APILoadingItem? {
|
||||
guard Thread.isMainThread else {
|
||||
print("⚠️ getFirstDisplayItem called from background thread")
|
||||
debugWarn("⚠️ getFirstDisplayItem called from background thread")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ struct APILoadingEffectView_Previews: PreviewProvider {
|
||||
.font(.title)
|
||||
|
||||
Button("测试按钮") {
|
||||
print("按钮被点击了!")
|
||||
debugInfo("按钮被点击了!")
|
||||
}
|
||||
.padding()
|
||||
.background(Color.blue)
|
||||
|
@@ -6,7 +6,7 @@ struct StringHashTest {
|
||||
|
||||
/// 测试哈希方法
|
||||
static func runTests() {
|
||||
print("🧪 开始测试字符串哈希方法...")
|
||||
debugInfo("🧪 开始测试字符串哈希方法...")
|
||||
|
||||
let testStrings = [
|
||||
"hello world",
|
||||
@@ -16,27 +16,27 @@ struct StringHashTest {
|
||||
]
|
||||
|
||||
for testString in testStrings {
|
||||
print("\n📝 测试字符串: \"\(testString)\"")
|
||||
debugInfo("\n📝 测试字符串: \"\(testString)\"")
|
||||
|
||||
// 测试 MD5
|
||||
let md5Result = testString.md5()
|
||||
print(" MD5: \(md5Result)")
|
||||
debugInfo(" MD5: \(md5Result)")
|
||||
|
||||
// 测试 SHA256 (iOS 13+)
|
||||
if #available(iOS 13.0, *) {
|
||||
let sha256Result = testString.sha256()
|
||||
print(" SHA256: \(sha256Result)")
|
||||
debugInfo(" SHA256: \(sha256Result)")
|
||||
} else {
|
||||
print(" SHA256: 不支持 (需要 iOS 13+)")
|
||||
debugInfo(" SHA256: 不支持 (需要 iOS 13+)")
|
||||
}
|
||||
}
|
||||
|
||||
print("\n✅ 哈希方法测试完成")
|
||||
debugInfo("\n✅ 哈希方法测试完成")
|
||||
}
|
||||
|
||||
/// 验证已知的哈希值
|
||||
static func verifyKnownHashes() {
|
||||
print("\n🔍 验证已知哈希值...")
|
||||
debugInfo("\n🔍 验证已知哈希值...")
|
||||
|
||||
// 验证 "hello world" 的 MD5 应该是 "5d41402abc4b2a76b9719d911017c592"
|
||||
let testString = "hello world"
|
||||
@@ -44,11 +44,11 @@ struct StringHashTest {
|
||||
let actualMD5 = testString.md5()
|
||||
|
||||
if actualMD5 == expectedMD5 {
|
||||
print("✅ MD5 验证通过: \(actualMD5)")
|
||||
debugInfo("✅ MD5 验证通过: \(actualMD5)")
|
||||
} else {
|
||||
print("❌ MD5 验证失败:")
|
||||
print(" 期望: \(expectedMD5)")
|
||||
print(" 实际: \(actualMD5)")
|
||||
debugError("❌ MD5 验证失败:")
|
||||
debugError(" 期望: \(expectedMD5)")
|
||||
debugError(" 实际: \(actualMD5)")
|
||||
}
|
||||
|
||||
// 验证 SHA256
|
||||
@@ -57,11 +57,11 @@ struct StringHashTest {
|
||||
let actualSHA256 = testString.sha256()
|
||||
|
||||
if actualSHA256 == expectedSHA256 {
|
||||
print("✅ SHA256 验证通过: \(actualSHA256)")
|
||||
debugInfo("✅ SHA256 验证通过: \(actualSHA256)")
|
||||
} else {
|
||||
print("❌ SHA256 验证失败:")
|
||||
print(" 期望: \(expectedSHA256)")
|
||||
print(" 实际: \(actualSHA256)")
|
||||
debugError("❌ SHA256 验证失败:")
|
||||
debugError(" 期望: \(expectedSHA256)")
|
||||
debugError(" 实际: \(actualSHA256)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,9 +75,9 @@ struct StringHashTest {
|
||||
StringHashTest.verifyKnownHashes()
|
||||
|
||||
// 或者在开发时快速测试
|
||||
print("Test MD5:", "hello".md5())
|
||||
debugInfo("Test MD5:", "hello".md5())
|
||||
if #available(iOS 13.0, *) {
|
||||
print("Test SHA256:", "hello".sha256())
|
||||
debugInfo("Test SHA256:", "hello".sha256())
|
||||
}
|
||||
|
||||
*/
|
@@ -67,11 +67,11 @@ struct FontManager {
|
||||
|
||||
/// 打印所有可用字体(调试用)
|
||||
static func printAllAvailableFonts() {
|
||||
print("=== 所有可用字体 ===")
|
||||
debugInfo("=== 所有可用字体 ===")
|
||||
for font in getAllAvailableFonts() {
|
||||
print(font)
|
||||
debugInfo(font)
|
||||
}
|
||||
print("==================")
|
||||
debugInfo("==================")
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -42,9 +42,9 @@ class LocalizationManager: ObservableObject {
|
||||
didSet {
|
||||
do {
|
||||
try KeychainManager.shared.storeString(currentLanguage.rawValue, forKey: "AppLanguage")
|
||||
} catch {
|
||||
print("❌ 保存语言设置失败: \(error)")
|
||||
}
|
||||
} catch {
|
||||
debugError("❌ 保存语言设置失败: \(error)")
|
||||
}
|
||||
// 通知视图更新
|
||||
objectWillChange.send()
|
||||
}
|
||||
@@ -56,7 +56,7 @@ class LocalizationManager: ObservableObject {
|
||||
do {
|
||||
savedLanguage = try KeychainManager.shared.retrieveString(forKey: "AppLanguage")
|
||||
} catch {
|
||||
print("❌ 读取语言设置失败: \(error)")
|
||||
debugError("❌ 读取语言设置失败: \(error)")
|
||||
savedLanguage = nil
|
||||
}
|
||||
|
||||
|
@@ -5,8 +5,8 @@ struct DESEncryptOCTest {
|
||||
|
||||
/// 测试 OC 版本的 DES 加密功能
|
||||
static func testOCDESEncryption() {
|
||||
print("🧪 开始测试 OC 版本的 DES 加密...")
|
||||
print(String(repeating: "=", count: 50))
|
||||
debugInfo("🧪 开始测试 OC 版本的 DES 加密...")
|
||||
debugInfo(String(repeating: "=", count: 50))
|
||||
|
||||
let key = "1ea53d260ecf11e7b56e00163e046a26"
|
||||
let testCases = [
|
||||
@@ -19,25 +19,25 @@ struct DESEncryptOCTest {
|
||||
|
||||
for testCase in testCases {
|
||||
if let encrypted = DESEncrypt.encryptUseDES(testCase, key: key) {
|
||||
print("✅ 加密成功:")
|
||||
print(" 原文: \"\(testCase)\"")
|
||||
print(" 密文: \(encrypted)")
|
||||
debugInfo("✅ 加密成功:")
|
||||
debugInfo(" 原文: \"\(testCase)\"")
|
||||
debugInfo(" 密文: \(encrypted)")
|
||||
|
||||
// 测试解密
|
||||
if let decrypted = DESEncrypt.decryptUseDES(encrypted, key: key) {
|
||||
let isMatch = decrypted == testCase
|
||||
print(" 解密: \"\(decrypted)\" \(isMatch ? "✅" : "❌")")
|
||||
debugInfo(" 解密: \"\(decrypted)\" \(isMatch ? "✅" : "❌")")
|
||||
} else {
|
||||
print(" 解密: 失败 ❌")
|
||||
debugError(" 解密: 失败 ❌")
|
||||
}
|
||||
} else {
|
||||
print("❌ 加密失败: \"\(testCase)\"")
|
||||
debugError("❌ 加密失败: \"\(testCase)\"")
|
||||
}
|
||||
print()
|
||||
debugInfo("")
|
||||
}
|
||||
|
||||
print(String(repeating: "=", count: 50))
|
||||
print("🏁 OC版本DES加密测试完成")
|
||||
debugInfo(String(repeating: "=", count: 50))
|
||||
debugInfo("🏁 OC版本DES加密测试完成")
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -54,23 +54,23 @@ final class DataMigrationManager {
|
||||
/// 执行数据迁移
|
||||
/// - Returns: 迁移结果
|
||||
func performMigration() -> MigrationResult {
|
||||
print("🔄 开始检查数据迁移...")
|
||||
debugInfo("🔄 开始检查数据迁移...")
|
||||
|
||||
// 检查是否已经迁移过
|
||||
if isMigrationCompleted() {
|
||||
print("✅ 数据已经迁移过,跳过迁移")
|
||||
debugInfo("✅ 数据已经迁移过,跳过迁移")
|
||||
return .alreadyMigrated
|
||||
}
|
||||
|
||||
// 检查是否有需要迁移的数据
|
||||
let legacyData = collectLegacyData()
|
||||
if legacyData.isEmpty {
|
||||
print("ℹ️ 没有发现需要迁移的数据")
|
||||
debugInfo("ℹ️ 没有发现需要迁移的数据")
|
||||
markMigrationCompleted()
|
||||
return .noDataToMigrate
|
||||
}
|
||||
|
||||
print("📦 发现需要迁移的数据: \(legacyData.keys.joined(separator: ", "))")
|
||||
debugInfo("📦 发现需要迁移的数据: \(legacyData.keys.joined(separator: ", "))")
|
||||
|
||||
do {
|
||||
// 执行迁移
|
||||
@@ -85,11 +85,11 @@ final class DataMigrationManager {
|
||||
// 标记迁移完成
|
||||
markMigrationCompleted()
|
||||
|
||||
print("✅ 数据迁移完成")
|
||||
debugInfo("✅ 数据迁移完成")
|
||||
return .completed
|
||||
|
||||
} catch {
|
||||
print("❌ 数据迁移失败: \(error)")
|
||||
debugError("❌ 数据迁移失败: \(error)")
|
||||
return .failed(error)
|
||||
}
|
||||
}
|
||||
@@ -157,9 +157,9 @@ final class DataMigrationManager {
|
||||
do {
|
||||
let accountModel = try JSONDecoder().decode(AccountModel.self, from: accountModelData)
|
||||
try keychain.store(accountModel, forKey: "account_model")
|
||||
print("✅ AccountModel 迁移成功")
|
||||
debugInfo("✅ AccountModel 迁移成功")
|
||||
} catch {
|
||||
print("❌ AccountModel 迁移失败: \(error)")
|
||||
debugError("❌ AccountModel 迁移失败: \(error)")
|
||||
// 如果 AccountModel 迁移失败,尝试从独立字段重建
|
||||
try migrateAccountModelFromIndependentFields(legacyData)
|
||||
}
|
||||
@@ -173,9 +173,9 @@ final class DataMigrationManager {
|
||||
do {
|
||||
let userInfo = try JSONDecoder().decode(UserInfo.self, from: userInfoData)
|
||||
try keychain.store(userInfo, forKey: "user_info")
|
||||
print("✅ UserInfo 迁移成功")
|
||||
debugInfo("✅ UserInfo 迁移成功")
|
||||
} catch {
|
||||
print("❌ UserInfo 迁移失败: \(error)")
|
||||
debugError("❌ UserInfo 迁移失败: \(error)")
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -183,7 +183,7 @@ final class DataMigrationManager {
|
||||
// 迁移语言设置
|
||||
if let appLanguage = legacyData[LegacyStorageKeys.appLanguage] as? String {
|
||||
try keychain.storeString(appLanguage, forKey: "AppLanguage")
|
||||
print("✅ 语言设置迁移成功")
|
||||
debugInfo("✅ 语言设置迁移成功")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ final class DataMigrationManager {
|
||||
private func migrateAccountModelFromIndependentFields(_ legacyData: [String: Any]) throws {
|
||||
guard let userId = legacyData[LegacyStorageKeys.userId] as? String,
|
||||
let accessToken = legacyData[LegacyStorageKeys.accessToken] as? String else {
|
||||
print("ℹ️ 没有足够的独立字段来重建 AccountModel")
|
||||
debugInfo("ℹ️ 没有足够的独立字段来重建 AccountModel")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ final class DataMigrationManager {
|
||||
)
|
||||
|
||||
try KeychainManager.shared.store(accountModel, forKey: "account_model")
|
||||
print("✅ 从独立字段重建 AccountModel 成功")
|
||||
debugInfo("✅ 从独立字段重建 AccountModel 成功")
|
||||
}
|
||||
|
||||
/// 验证迁移结果
|
||||
@@ -240,7 +240,7 @@ final class DataMigrationManager {
|
||||
}
|
||||
}
|
||||
|
||||
print("✅ 迁移数据验证成功")
|
||||
debugInfo("✅ 迁移数据验证成功")
|
||||
}
|
||||
|
||||
/// 清理旧数据
|
||||
@@ -249,11 +249,11 @@ final class DataMigrationManager {
|
||||
|
||||
for key in keys {
|
||||
userDefaults.removeObject(forKey: key)
|
||||
print("🗑️ 清理旧数据: \(key)")
|
||||
debugInfo("🗑️ 清理旧数据: \(key)")
|
||||
}
|
||||
|
||||
userDefaults.synchronize()
|
||||
print("✅ 旧数据清理完成")
|
||||
debugInfo("✅ 旧数据清理完成")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,13 +287,13 @@ extension DataMigrationManager {
|
||||
|
||||
switch migrationResult {
|
||||
case .completed:
|
||||
print("🎉 应用启动时数据迁移完成")
|
||||
debugInfo("🎉 应用启动时数据迁移完成")
|
||||
case .alreadyMigrated:
|
||||
break // 静默处理
|
||||
case .noDataToMigrate:
|
||||
break // 静默处理
|
||||
case .failed(let error):
|
||||
print("⚠️ 应用启动时数据迁移失败: \(error)")
|
||||
debugError("⚠️ 应用启动时数据迁移失败: \(error)")
|
||||
// 这里可以添加错误上报或降级策略
|
||||
}
|
||||
}
|
||||
@@ -307,9 +307,9 @@ extension DataMigrationManager {
|
||||
/// 调试:打印旧数据信息
|
||||
func debugPrintLegacyData() {
|
||||
let legacyData = collectLegacyData()
|
||||
print("🔍 旧版本数据:")
|
||||
debugInfo("🔍 旧版本数据:")
|
||||
for (key, value) in legacyData {
|
||||
print(" - \(key): \(type(of: value))")
|
||||
debugInfo(" - \(key): \(type(of: value))")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,7 +322,7 @@ extension DataMigrationManager {
|
||||
userDefaults.set("zh-Hans", forKey: LegacyStorageKeys.appLanguage)
|
||||
userDefaults.synchronize()
|
||||
|
||||
print("🧪 已创建测试用的旧版本数据")
|
||||
debugInfo("🧪 已创建测试用的旧版本数据")
|
||||
}
|
||||
|
||||
/// 调试:清除所有迁移相关数据
|
||||
@@ -331,7 +331,7 @@ extension DataMigrationManager {
|
||||
do {
|
||||
try KeychainManager.shared.clearAll()
|
||||
} catch {
|
||||
print("❌ 清除 Keychain 数据失败: \(error)")
|
||||
debugError("❌ 清除 Keychain 数据失败: \(error)")
|
||||
}
|
||||
|
||||
// 清除 UserDefaults 数据
|
||||
@@ -350,7 +350,7 @@ extension DataMigrationManager {
|
||||
}
|
||||
userDefaults.synchronize()
|
||||
|
||||
print("🧪 已清除所有迁移相关数据")
|
||||
debugInfo("🧪 已清除所有迁移相关数据")
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -108,7 +108,7 @@ final class KeychainManager {
|
||||
throw KeychainError.keychainOperationFailed(status)
|
||||
}
|
||||
|
||||
print("🔐 Keychain 存储成功: \(key)")
|
||||
debugInfo("🔐 Keychain 存储成功: \(key)")
|
||||
}
|
||||
|
||||
/// 从 Keychain 检索 Codable 对象
|
||||
@@ -137,7 +137,7 @@ final class KeychainManager {
|
||||
// 4. 解码数据
|
||||
do {
|
||||
let object = try JSONDecoder().decode(type, from: data)
|
||||
print("🔐 Keychain 读取成功: \(key)")
|
||||
debugInfo("🔐 Keychain 读取成功: \(key)")
|
||||
return object
|
||||
} catch {
|
||||
throw KeychainError.decodingFailed(error)
|
||||
@@ -176,7 +176,7 @@ final class KeychainManager {
|
||||
|
||||
switch status {
|
||||
case errSecSuccess:
|
||||
print("🔐 Keychain 更新成功: \(key)")
|
||||
debugInfo("🔐 Keychain 更新成功: \(key)")
|
||||
|
||||
case errSecItemNotFound:
|
||||
// 如果项目不存在,则创建新项目
|
||||
@@ -196,7 +196,7 @@ final class KeychainManager {
|
||||
|
||||
switch status {
|
||||
case errSecSuccess:
|
||||
print("🔐 Keychain 删除成功: \(key)")
|
||||
debugInfo("🔐 Keychain 删除成功: \(key)")
|
||||
|
||||
case errSecItemNotFound:
|
||||
// 项目不存在,视为删除成功
|
||||
@@ -231,7 +231,7 @@ final class KeychainManager {
|
||||
|
||||
switch status {
|
||||
case errSecSuccess, errSecItemNotFound:
|
||||
print("🔐 Keychain 清除完成")
|
||||
debugInfo("🔐 Keychain 清除完成")
|
||||
|
||||
default:
|
||||
throw KeychainError.keychainOperationFailed(status)
|
||||
@@ -353,9 +353,9 @@ extension KeychainManager {
|
||||
/// 打印所有存储的键(仅用于调试)
|
||||
func debugPrintAllKeys() {
|
||||
let keys = debugListAllKeys()
|
||||
print("🔐 Keychain 中存储的键:")
|
||||
debugInfo("🔐 Keychain 中存储的键:")
|
||||
for key in keys {
|
||||
print(" - \(key)")
|
||||
debugInfo(" - \(key)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -66,20 +66,20 @@ struct UserAgreementView: View {
|
||||
UserAgreementView(
|
||||
isAgreed: .constant(true),
|
||||
onUserServiceTapped: {
|
||||
print("User Service Agreement tapped")
|
||||
debugInfo("User Service Agreement tapped")
|
||||
},
|
||||
onPrivacyPolicyTapped: {
|
||||
print("Privacy Policy tapped")
|
||||
debugInfo("Privacy Policy tapped")
|
||||
}
|
||||
)
|
||||
|
||||
UserAgreementView(
|
||||
isAgreed: .constant(true),
|
||||
onUserServiceTapped: {
|
||||
print("User Service Agreement tapped")
|
||||
debugInfo("User Service Agreement tapped")
|
||||
},
|
||||
onPrivacyPolicyTapped: {
|
||||
print("Privacy Policy tapped")
|
||||
debugInfo("Privacy Policy tapped")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@@ -207,7 +207,7 @@ struct IDLoginView: View {
|
||||
|
||||
#if DEBUG
|
||||
// 移除测试用的硬编码凭据
|
||||
print("🐛 Debug模式: 已移除硬编码测试凭据")
|
||||
debugInfo("🐛 Debug模式: 已移除硬编码测试凭据")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import SwiftUI
|
||||
|
||||
struct MeView: View {
|
||||
@State private var showLogoutConfirmation = false
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
ScrollView {
|
||||
@@ -48,7 +49,9 @@ struct MeView: View {
|
||||
.padding(.top, 40)
|
||||
|
||||
// 退出登录按钮
|
||||
Button(action: {}) {
|
||||
Button(action: {
|
||||
showLogoutConfirmation = true
|
||||
}) {
|
||||
HStack {
|
||||
Image(systemName: "rectangle.portrait.and.arrow.right")
|
||||
.font(.system(size: 16))
|
||||
@@ -72,6 +75,27 @@ struct MeView: View {
|
||||
}
|
||||
}
|
||||
.ignoresSafeArea(.container, edges: .top)
|
||||
.alert("确认退出", isPresented: $showLogoutConfirmation) {
|
||||
Button("取消", role: .cancel) { }
|
||||
Button("退出", role: .destructive) {
|
||||
performLogout()
|
||||
}
|
||||
} message: {
|
||||
Text("确定要退出登录吗?")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 退出登录方法
|
||||
private func performLogout() {
|
||||
debugInfo("🔓 开始执行退出登录...")
|
||||
|
||||
// 清除所有认证数据(包括 keychain 中的内容)
|
||||
UserInfoManager.clearAllAuthenticationData()
|
||||
|
||||
// 发送通知重置 window root 为 login view
|
||||
NotificationCenter.default.post(name: .homeLogout, object: nil)
|
||||
|
||||
debugInfo("✅ 退出登录完成")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,4 +135,4 @@ struct MenuItemView: View {
|
||||
|
||||
#Preview {
|
||||
MeView()
|
||||
}
|
||||
}
|
||||
|
@@ -188,12 +188,12 @@ struct SettingRowView: View {
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
SettingView(
|
||||
store: Store(
|
||||
initialState: SettingFeature.State()
|
||||
) {
|
||||
SettingFeature()
|
||||
}
|
||||
)
|
||||
}
|
||||
//#Preview {
|
||||
// SettingView(
|
||||
// store: Store(
|
||||
// initialState: SettingFeature.State()
|
||||
// ) {
|
||||
// SettingFeature()
|
||||
// }
|
||||
// )
|
||||
//}
|
||||
|
@@ -21,7 +21,7 @@ struct yanaApp: App {
|
||||
}
|
||||
#endif
|
||||
|
||||
print("🛠 原生URLSession测试开始")
|
||||
debugInfo("🛠 原生URLSession测试开始")
|
||||
}
|
||||
|
||||
var body: some Scene {
|
||||
|
@@ -163,10 +163,10 @@ final class yanaAPITests: XCTestCase {
|
||||
XCTAssertEqual(accountModel?.uid, "3184", "真实API数据的UID应该正确")
|
||||
XCTAssertTrue(accountModel?.hasValidAuthentication ?? false, "真实API数据应该有有效认证")
|
||||
|
||||
print("✅ 真实API数据测试通过")
|
||||
print(" UID: \(accountModel?.uid ?? "nil")")
|
||||
print(" Access Token存在: \(accountModel?.accessToken != nil)")
|
||||
print(" Token类型: \(accountModel?.tokenType ?? "nil")")
|
||||
debugInfo("✅ 真实API数据测试通过")
|
||||
debugInfo(" UID: \(accountModel?.uid ?? "nil")")
|
||||
debugInfo(" Access Token存在: \(accountModel?.accessToken != nil)")
|
||||
debugInfo(" Token类型: \(accountModel?.tokenType ?? "nil")")
|
||||
|
||||
} catch {
|
||||
XCTFail("解析真实API数据失败: \(error)")
|
||||
|
Reference in New Issue
Block a user