626 lines
17 KiB
Markdown
626 lines
17 KiB
Markdown
# 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<Self> {
|
|
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<Void, Never>?
|
|
|
|
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<T: APIRequestProtocol>(
|
|
_ request: T,
|
|
maxRetries: Int = 3
|
|
) async throws -> T.Response {
|
|
var lastError: Error?
|
|
|
|
for attempt in 0..<maxRetries {
|
|
do {
|
|
return try await apiService.request(request)
|
|
} catch {
|
|
lastError = error
|
|
if attempt < maxRetries - 1 {
|
|
try await Task.sleep(nanoseconds: UInt64(pow(2.0, Double(attempt))) * 1_000_000_000)
|
|
}
|
|
}
|
|
}
|
|
|
|
throw lastError!
|
|
}
|
|
```
|
|
|
|
## 📋 API接口列表
|
|
|
|
### 当前可用接口
|
|
|
|
| 接口名称 | 端点 | 方法 | 说明 |
|
|
|---------|------|------|------|
|
|
| 配置获取 | `/client/config` | GET | 获取客户端配置信息 |
|
|
| 初始化配置 | `/client/init` | GET | 获取初始化配置 |
|
|
| 用户登录 | `/auth/login` | POST | 用户登录认证 |
|
|
|
|
### 接口扩展
|
|
|
|
要添加新的 API 接口,请按以下步骤操作:
|
|
|
|
1. **在 `APIEndpoints.swift` 中添加端点**:
|
|
```swift
|
|
enum APIEndpoint: String, CaseIterable {
|
|
case newEndpoint = "/new/endpoint"
|
|
// ...
|
|
}
|
|
```
|
|
|
|
2. **创建请求模型**:
|
|
```swift
|
|
struct NewRequest: APIRequestProtocol {
|
|
typealias Response = NewResponse
|
|
|
|
let endpoint = "/new/endpoint"
|
|
let method: HTTPMethod = .POST
|
|
let includeBaseParameters = true
|
|
// ... 其他属性
|
|
}
|
|
```
|
|
|
|
3. **创建响应模型**:
|
|
```swift
|
|
struct NewResponse: Codable {
|
|
let success: Bool
|
|
let data: SomeDataModel?
|
|
let message: String?
|
|
}
|
|
```
|
|
|
|
## 📝 示例代码
|
|
|
|
### 完整的登录功能示例
|
|
|
|
```swift
|
|
import SwiftUI
|
|
import ComposableArchitecture
|
|
|
|
// MARK: - Login Feature
|
|
@Reducer
|
|
struct LoginFeature {
|
|
@ObservableState
|
|
struct State: Equatable {
|
|
var username = ""
|
|
var password = ""
|
|
var isLoading = false
|
|
var isLoggedIn = false
|
|
var errorMessage: String?
|
|
}
|
|
|
|
enum Action: Equatable {
|
|
case usernameChanged(String)
|
|
case passwordChanged(String)
|
|
case loginButtonTapped
|
|
case loginResponse(Result<LoginResponse, APIError>)
|
|
}
|
|
|
|
@Dependency(\.apiService) var apiService
|
|
|
|
var body: some ReducerOf<Self> {
|
|
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<LoginFeature>
|
|
|
|
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. 更新相关文档
|
|
|
|
---
|
|
|
|
**注意**: 本文档基于当前项目架构编写,如有架构变更请及时更新文档内容。 |