# Yana iOS API 使用指南 ## 📋 目录 - [架构概览](#架构概览) - [快速开始](#快速开始) - [环境配置](#环境配置) - [请求方式](#请求方式) - [错误处理](#错误处理) - [安全机制](#安全机制) - [最佳实践](#最佳实践) - [API接口列表](#api接口列表) - [示例代码](#示例代码) ## 🏗️ 架构概览 Yana iOS 项目采用基于 **TCA (The Composable Architecture)** 的现代化 API 架构设计: ``` ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ SwiftUI View │───▶│ TCA Reducer │───▶│ API Service │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ │ App State │ │ Network Layer │ └─────────────────┘ └─────────────────┘ ``` ### 核心组件 - **APIService**: 网络请求核心服务 - **APIModels**: 数据模型和协议定义 - **APIEndpoints**: API 端点配置 - **APILogger**: 请求日志记录 - **BaseRequest**: 基础请求参数管理 ## 🚀 快速开始 ### 1. 基本使用 ```swift import SwiftUI struct ContentView: View { @State private var isLoading = false @State private var result = "" var body: some View { VStack { Button("发起 API 请求") { Task { await makeAPIRequest() } } .disabled(isLoading) if isLoading { ProgressView("请求中...") } Text(result) } } private func makeAPIRequest() async { isLoading = true do { // 创建 API 服务实例 let apiService = LiveAPIService() // 创建请求 let request = ConfigRequest() // 发起请求 let response = try await apiService.request(request) await MainActor.run { result = "请求成功: \(response)" isLoading = false } } catch { await MainActor.run { result = "请求失败: \(error.localizedDescription)" isLoading = false } } } } ``` ### 2. TCA 集成使用 ```swift import ComposableArchitecture @Reducer struct APIFeature { @ObservableState struct State: Equatable { var data: ConfigResponse? var isLoading = false var errorMessage: String? } enum Action: Equatable { case loadConfig case configLoaded(ConfigResponse) case loadingFailed(String) } @Dependency(\.apiService) var apiService var body: some ReducerOf { Reduce { state, action in switch action { case .loadConfig: state.isLoading = true state.errorMessage = nil return .run { send in do { let request = ConfigRequest() let response = try await apiService.request(request) await send(.configLoaded(response)) } catch { await send(.loadingFailed(error.localizedDescription)) } } case let .configLoaded(response): state.isLoading = false state.data = response return .none case let .loadingFailed(error): state.isLoading = false state.errorMessage = error return .none } } } } ``` ## ⚙️ 环境配置 ### 服务器环境 | 环境 | 地址 | 说明 | |------|------|------| | 测试环境 | `http://beta.api.molistar.xyz` | 开发测试服务器 | | 生产环境 | `https://api.hfighting.com` | 正式服务器 | ### 配置参数 ```swift struct APIConfiguration { static let baseURL = "http://beta.api.molistar.xyz" static let timeout: TimeInterval = 30.0 static let maxDataSize: Int = 50 * 1024 * 1024 // 50MB } ``` ### 默认请求头 所有请求都会自动添加以下请求头: ```swift static var defaultHeaders: [String: String] { return [ "Content-Type": "application/json", "Accept": "application/json", "Accept-Encoding": "gzip, br", "Accept-Language": Locale.current.languageCode ?? "en", "App-Version": Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0" ] } ``` ## 📡 请求方式 ### 1. GET 请求 ```swift struct ConfigRequest: APIRequestProtocol { typealias Response = ConfigResponse let endpoint = "/client/config" let method: HTTPMethod = .GET let includeBaseParameters = true let queryParameters: [String: String]? = nil let bodyParameters: [String: Any]? = nil let headers: [String: String]? = nil let timeout: TimeInterval = 30.0 } ``` ### 2. POST 请求 ```swift struct LoginRequest: APIRequestProtocol { typealias Response = LoginResponse let endpoint = "/auth/login" let method: HTTPMethod = .POST let includeBaseParameters = true let queryParameters: [String: String]? = nil let bodyParameters: [String: Any]? let headers: [String: String]? = nil let timeout: TimeInterval = 30.0 init(username: String, password: String) { self.bodyParameters = [ "username": username, "password": password ] } } ``` ### 3. 基础参数说明 每个请求都会自动包含以下基础参数: ```swift struct BaseRequest { let acceptLanguage: String // 用户语言偏好 let os: String = "iOS" // 操作系统类型 let osVersion: String // 系统版本号 let netType: Int // 网络类型 (WiFi=2, 蜂窝=1) let ispType: String // 运营商类型 let channel: String // 应用分发渠道 let model: String // 设备型号 let deviceId: String // 设备唯一标识 let appVersion: String // 应用版本 let app: String // 应用名称 let lang: String // 语言代码 let mcc: String? // 移动国家代码 let pubSign: String // 安全签名 } ``` ## ❌ 错误处理 ### 错误类型 ```swift enum APIError: Error, Equatable { case invalidURL // 无效的 URL case noData // 没有收到数据 case decodingError(String) // 数据解析失败 case networkError(String) // 网络错误 case httpError(statusCode: Int, message: String?) // HTTP 错误 case timeout // 请求超时 case resourceTooLarge // 响应数据过大 case unknown(String) // 未知错误 } ``` ### 错误处理示例 ```swift do { let response = try await apiService.request(request) // 处理成功响应 } catch let apiError as APIError { switch apiError { case .networkError(let message): print("网络错误: \(message)") case .timeout: print("请求超时,请检查网络连接") case .httpError(let statusCode, let message): print("服务器错误 \(statusCode): \(message ?? "未知错误")") case .decodingError(let message): print("数据解析失败: \(message)") default: print("其他错误: \(apiError.localizedDescription)") } } catch { print("未知错误: \(error)") } ``` ## 🔐 安全机制 ### 签名生成流程 1. **参数过滤**: 移除系统级参数 2. **参数排序**: 按字典 key 升序排序 3. **字符串拼接**: `"key0=value0&key1=value1"` 4. **添加密钥**: 拼接 `key=rpbs6us1m8r2j9g6u06ff2bo18orwaya` 5. **MD5加密**: 生成大写 MD5 签名 ### 认证头部 ```swift // 用户认证相关头部(如果用户已登录) if let userId = UserInfoManager.getCurrentUserId() { headers["pub_uid"] = userId } if let userTicket = UserInfoManager.getCurrentUserTicket() { headers["pub_ticket"] = userTicket } ``` ## 💡 最佳实践 ### 1. 错误处理 ```swift // ✅ 推荐:完整的错误处理 do { let response = try await apiService.request(request) // 处理成功响应 } catch let urlError as URLError { switch urlError.code { case .notConnectedToInternet: showAlert("网络不可用,请检查网络连接") case .timedOut: showAlert("请求超时,请重试") default: showAlert("网络错误: \(urlError.localizedDescription)") } } catch let apiError as APIError { showAlert(apiError.localizedDescription) } catch { showAlert("未知错误: \(error)") } ``` ### 2. 主线程更新 UI ```swift // ✅ 推荐:使用 MainActor 更新 UI await MainActor.run { self.isLoading = false self.data = response } ``` ### 3. 取消请求 ```swift // ✅ 推荐:支持取消的请求 struct ContentView: View { @State private var task: Task? private func makeRequest() { task = Task { do { let response = try await apiService.request(request) // 处理响应 } catch { if !Task.isCancelled { // 处理错误 } } } } private func cancelRequest() { task?.cancel() } } ``` ### 4. 重试机制 ```swift // ✅ 推荐:实现重试逻辑 func requestWithRetry( _ request: T, maxRetries: Int = 3 ) async throws -> T.Response { var lastError: Error? for attempt in 0..) } @Dependency(\.apiService) var apiService var body: some ReducerOf { Reduce { state, action in switch action { case let .usernameChanged(username): state.username = username return .none case let .passwordChanged(password): state.password = password return .none case .loginButtonTapped: state.isLoading = true state.errorMessage = nil let request = LoginRequest( username: state.username, password: state.password ) return .run { send in do { let response = try await apiService.request(request) await send(.loginResponse(.success(response))) } catch let error as APIError { await send(.loginResponse(.failure(error))) } catch { await send(.loginResponse(.failure(.unknown(error.localizedDescription)))) } } case let .loginResponse(.success(response)): state.isLoading = false state.isLoggedIn = response.success if !response.success { state.errorMessage = response.message } return .none case let .loginResponse(.failure(error)): state.isLoading = false state.errorMessage = error.localizedDescription return .none } } } } // MARK: - Login View struct LoginView: View { let store: StoreOf var body: some View { WithViewStore(self.store, observe: { $0 }) { viewStore in VStack(spacing: 20) { TextField("用户名", text: viewStore.binding( get: \.username, send: LoginFeature.Action.usernameChanged )) .textFieldStyle(RoundedBorderTextFieldStyle()) SecureField("密码", text: viewStore.binding( get: \.password, send: LoginFeature.Action.passwordChanged )) .textFieldStyle(RoundedBorderTextFieldStyle()) Button("登录") { viewStore.send(.loginButtonTapped) } .disabled(viewStore.isLoading || viewStore.username.isEmpty || viewStore.password.isEmpty) if viewStore.isLoading { ProgressView("登录中...") } if let errorMessage = viewStore.errorMessage { Text(errorMessage) .foregroundColor(.red) } } .padding() } } } // MARK: - Request & Response Models struct LoginRequest: APIRequestProtocol { typealias Response = LoginResponse let endpoint = "/auth/login" let method: HTTPMethod = .POST let includeBaseParameters = true let queryParameters: [String: String]? = nil let bodyParameters: [String: Any]? let headers: [String: String]? = nil let timeout: TimeInterval = 30.0 init(username: String, password: String) { self.bodyParameters = [ "username": username, "password": password ] } } struct LoginResponse: Codable { let success: Bool let message: String? let data: UserData? } struct UserData: Codable { let userId: String let username: String let token: String } ``` ## 🔧 调试和日志 ### 启用详细日志 API 请求和响应会自动记录到控制台,包括: - 请求 URL 和参数 - 请求头信息 - 响应状态码和数据 - 请求耗时 - 错误信息 ### 日志示例 ``` 🚀 API Request: GET /client/config 📋 Headers: ["Content-Type": "application/json", "Accept": "application/json"] 📊 Query Parameters: ["deviceId": "ABC123", "appVersion": "1.0.0"] ✅ API Response: 200 OK (0.45s) 📦 Response Size: 1.2KB 📄 Response Data: {"success": true, "data": {...}} ``` ## 📚 相关文档 - [API 规则详解](yana/APIs/API%20rule.md) - [集成指南](yana/APIs/Integration-Guide.md) - [TCA 官方文档](https://github.com/pointfreeco/swift-composable-architecture) ## 🤝 贡献指南 1. 遵循现有的代码风格和架构模式 2. 为新的 API 接口添加完整的文档和示例 3. 确保所有请求都包含适当的错误处理 4. 添加单元测试覆盖新功能 5. 更新相关文档 --- **注意**: 本文档基于当前项目架构编写,如有架构变更请及时更新文档内容。