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

View File

@@ -16,7 +16,7 @@ target 'yana' do
# pod 'NELocalConversationUIKit', '10.6.1' # 本地会话列表组件。
# Networks
pod 'Alamofire'
# pod 'Alamofire'
end
post_install do |installer|

View File

@@ -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

View File

@@ -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 */

View File

@@ -7,7 +7,7 @@
<key>yana.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>3</integer>
<integer>1</integer>
</dict>
</dict>
</dict>

View File

@@ -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
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)
}

View File

@@ -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",

View File

@@ -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:

View File

@@ -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:

View File

@@ -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,

View File

@@ -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)
}

View File

@@ -18,18 +18,33 @@ public class LogManager {
/// - Parameters:
/// - level:
/// - message:
/// - onlyRelease: Release falseDebug
/// - 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())
}

View File

@@ -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)

View File

@@ -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())
}
*/

View File

@@ -67,11 +67,11 @@ struct FontManager {
///
static func printAllAvailableFonts() {
print("=== 所有可用字体 ===")
debugInfo("=== 所有可用字体 ===")
for font in getAllAvailableFonts() {
print(font)
debugInfo(font)
}
print("==================")
debugInfo("==================")
}
}

View File

@@ -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
}

View File

@@ -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加密测试完成")
}
}

View File

@@ -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

View File

@@ -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)")
}
}
}

View File

@@ -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")
}
)
}

View File

@@ -207,7 +207,7 @@ struct IDLoginView: View {
#if DEBUG
//
print("🐛 Debug模式: 已移除硬编码测试凭据")
debugInfo("🐛 Debug模式: 已移除硬编码测试凭据")
#endif
}
}

View File

@@ -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()
}
}

View File

@@ -188,12 +188,12 @@ struct SettingRowView: View {
}
}
#Preview {
SettingView(
store: Store(
initialState: SettingFeature.State()
) {
SettingFeature()
}
)
}
//#Preview {
// SettingView(
// store: Store(
// initialState: SettingFeature.State()
// ) {
// SettingFeature()
// }
// )
//}

View File

@@ -21,7 +21,7 @@ struct yanaApp: App {
}
#endif
print("🛠 原生URLSession测试开始")
debugInfo("🛠 原生URLSession测试开始")
}
var body: some Scene {

View File

@@ -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)")