first commit for e-party
This commit is contained in:
626
API-README.md
Normal file
626
API-README.md
Normal file
@@ -0,0 +1,626 @@
|
||||
# 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. 更新相关文档
|
||||
|
||||
---
|
||||
|
||||
**注意**: 本文档基于当前项目架构编写,如有架构变更请及时更新文档内容。
|
Reference in New Issue
Block a user