# **dynamic/square/latestDynamics API 文档** ## **概述** `dynamic/square/latestDynamics` 是获取朋友圈动态最新列表的 API 接口,用于获取用户动态内容的最新更新。 ## **接口信息** | 属性 | 值 | |------|-----| | **接口路径** | `GET /dynamic/square/latestDynamics` | | **请求方法** | `GET` | | **认证要求** | 需要 `pub_uid` 和 `pub_ticket` | | **内容类型** | `application/json` | ## **请求参数** | 参数名 | 类型 | 必填 | 描述 | 示例值 | |--------|------|------|------|--------| | `dynamicId` | `String` | 否 | 最新动态的ID,用于分页加载。首次请求传空字符串 | `""` 或 `"123456"` | | `pageSize` | `String` | 是 | 每页返回的数据数量 | `"20"` | | `types` | `String` | 是 | 动态内容类型,多个类型用逗号分隔 | `"0,2"` | ### **types 参数说明** - `0`: 纯文字动态 - `2`: 图片动态 ## **响应数据结构** ### **成功响应 (200)** ```json { "code": 200, "message": "success", "data": { "dynamicList": [ { "dynamicId": "123456", "uid": "789012", "nick": "用户昵称", "avatar": "https://example.com/avatar.jpg", "gender": 1, "age": 25, "type": 0, "content": "动态内容文字", "likeCount": "15", "isLike": false, "commentCount": "3", "publishTime": "2024-01-15 10:30:00", "worldId": 456, "worldName": "话题名称", "squareTop": false, "topicTop": false, "newUser": false, "defUser": 0, "inRoomUid": "", "dynamicResList": [ { "resUrl": "https://example.com/image.jpg", "format": "jpg", "width": 720, "height": 960 } ], "userVipInfoVO": { "vipLevel": 3, "vipExpire": "2024-12-31" }, "headwearPic": "https://example.com/headwear.png", "headwearEffect": "https://example.com/effect.svga", "headwearType": 1, "expertLevelPic": "https://example.com/expert_lv3.png", "charmLevelPic": "https://example.com/charm_lv2.png", "nameplatePic": "https://example.com/nameplate.png", "nameplateWord": "自定义铭牌", "isCustomWord": true, "labelList": ["新人", "活跃"] } ], "nextDynamicId": "123455" } } ``` ### **错误响应** ```json { "code": 400, "message": "参数错误", "data": null } ``` ## **Swift 实现示例** ### **1. 数据模型定义** ```swift // MARK: - 响应数据模型 struct MomentsLatestResponse: Codable { let code: Int let message: String let data: MomentsListData? } struct MomentsListData: Codable { let dynamicList: [MomentsInfo] let nextDynamicId: String } struct MomentsInfo: Codable { let dynamicId: String let uid: String let nick: String let avatar: String let gender: Int let age: Int let type: Int let content: String let likeCount: String let isLike: Bool let commentCount: String let publishTime: String let worldId: Int let worldName: String? let squareTop: Bool let topicTop: Bool let newUser: Bool let defUser: Int let inRoomUid: String? let dynamicResList: [MomentsPicture]? let userVipInfoVO: UserVipInfo? let headwearPic: String? let headwearEffect: String? let headwearType: Int? let expertLevelPic: String? let charmLevelPic: String? let nameplatePic: String? let nameplateWord: String? let isCustomWord: Bool? let labelList: [String]? } struct MomentsPicture: Codable { let resUrl: String let format: String let width: CGFloat let height: CGFloat } struct UserVipInfo: Codable { let vipLevel: Int let vipExpire: String? } // MARK: - 内容类型枚举 enum MomentsContentType: Int, CaseIterable { case text = 0 // 纯文字 case picture = 2 // 图片 } ``` ### **2. API 服务实现** ```swift import Foundation import Combine class MomentsAPIService { private let baseURL = "https://api.yourapp.com" private let session = URLSession.shared // MARK: - 获取最新动态列表 func fetchLatestMoments( dynamicId: String = "", pageSize: Int = 20, types: [MomentsContentType] = [.text, .picture] ) -> AnyPublisher { // 构建请求参数 var components = URLComponents(string: "\(baseURL)/dynamic/square/latestDynamics")! components.queryItems = [ URLQueryItem(name: "dynamicId", value: dynamicId), URLQueryItem(name: "pageSize", value: String(pageSize)), URLQueryItem(name: "types", value: types.map { String($0.rawValue) }.joined(separator: ",")) ] guard let url = components.url else { return Fail(error: APIError.invalidURL) .eraseToAnyPublisher() } var request = URLRequest(url: url) request.httpMethod = "GET" // 添加认证头 if let uid = AuthManager.shared.currentUID { request.setValue(uid, forHTTPHeaderField: "pub_uid") } if let ticket = AuthManager.shared.currentTicket { request.setValue(ticket, forHTTPHeaderField: "pub_ticket") } // 添加其他公共头 request.setValue("application/json", forHTTPHeaderField: "Accept") request.setValue(AppInfo.version, forHTTPHeaderField: "App-Version") request.setValue(Locale.current.languageCode ?? "en", forHTTPHeaderField: "Accept-Language") return session.dataTaskPublisher(for: request) .map(\.data) .decode(type: MomentsLatestResponse.self, decoder: JSONDecoder()) .compactMap { response in guard response.code == 200 else { throw APIError.serverError(response.code, response.message) } return response.data } .receive(on: DispatchQueue.main) .eraseToAnyPublisher() } } // MARK: - 错误类型定义 enum APIError: Error, LocalizedError { case invalidURL case noData case serverError(Int, String) case networkError(Error) var errorDescription: String? { switch self { case .invalidURL: return "无效的URL" case .noData: return "无数据返回" case .serverError(let code, let message): return "服务器错误 (\(code)): \(message)" case .networkError(let error): return "网络错误: \(error.localizedDescription)" } } } ``` ### **3. ViewModel 实现** ```swift import Foundation import Combine @MainActor class MomentsLatestViewModel: ObservableObject { @Published var moments: [MomentsInfo] = [] @Published var isLoading = false @Published var hasMoreData = true @Published var errorMessage: String? private var nextDynamicId = "" private let apiService = MomentsAPIService() private var cancellables = Set() // MARK: - 加载最新数据 func loadLatestMoments() { loadMoments(isRefresh: true) } // MARK: - 加载更多数据 func loadMoreMoments() { guard hasMoreData && !isLoading else { return } loadMoments(isRefresh: false) } // MARK: - 私有方法:统一加载逻辑 private func loadMoments(isRefresh: Bool) { isLoading = true errorMessage = nil let dynamicId = isRefresh ? "" : nextDynamicId apiService.fetchLatestMoments( dynamicId: dynamicId, pageSize: 20, types: [.text, .picture] ) .sink( receiveCompletion: { [weak self] completion in self?.isLoading = false if case .failure(let error) = completion { self?.errorMessage = error.localizedDescription } }, receiveValue: { [weak self] data in if isRefresh { self?.moments = data.dynamicList } else { self?.moments.append(contentsOf: data.dynamicList) } self?.nextDynamicId = data.nextDynamicId self?.hasMoreData = !data.dynamicList.isEmpty } ) .store(in: &cancellables) } // MARK: - 点赞操作 func toggleLike(for momentId: String) { // 实现点赞逻辑 guard let index = moments.firstIndex(where: { $0.dynamicId == momentId }) else { return } moments[index] = MomentsInfo( dynamicId: moments[index].dynamicId, uid: moments[index].uid, nick: moments[index].nick, avatar: moments[index].avatar, gender: moments[index].gender, age: moments[index].age, type: moments[index].type, content: moments[index].content, likeCount: moments[index].isLike ? String(max(0, Int(moments[index].likeCount) ?? 0 - 1)) : String((Int(moments[index].likeCount) ?? 0) + 1), isLike: !moments[index].isLike, commentCount: moments[index].commentCount, publishTime: moments[index].publishTime, worldId: moments[index].worldId, worldName: moments[index].worldName, squareTop: moments[index].squareTop, topicTop: moments[index].topicTop, newUser: moments[index].newUser, defUser: moments[index].defUser, inRoomUid: moments[index].inRoomUid, dynamicResList: moments[index].dynamicResList, userVipInfoVO: moments[index].userVipInfoVO, headwearPic: moments[index].headwearPic, headwearEffect: moments[index].headwearEffect, headwearType: moments[index].headwearType, expertLevelPic: moments[index].expertLevelPic, charmLevelPic: moments[index].charmLevelPic, nameplatePic: moments[index].nameplatePic, nameplateWord: moments[index].nameplateWord, isCustomWord: moments[index].isCustomWord, labelList: moments[index].labelList ) } } ``` ### **4. SwiftUI 视图实现** ```swift import SwiftUI struct MomentsLatestView: View { @StateObject private var viewModel = MomentsLatestViewModel() var body: some View { NavigationView { List { ForEach(viewModel.moments, id: \.dynamicId) { moment in MomentCardView(moment: moment) { viewModel.toggleLike(for: moment.dynamicId) } .onAppear { // 当显示最后一个元素时加载更多 if moment.dynamicId == viewModel.moments.last?.dynamicId { viewModel.loadMoreMoments() } } } if viewModel.isLoading { HStack { Spacer() ProgressView() Spacer() } } } .refreshable { viewModel.loadLatestMoments() } .navigationTitle("最新动态") .onAppear { if viewModel.moments.isEmpty { viewModel.loadLatestMoments() } } .alert("错误", isPresented: .constant(viewModel.errorMessage != nil)) { Button("确定") { viewModel.errorMessage = nil } } message: { Text(viewModel.errorMessage ?? "") } } } } struct MomentCardView: View { let moment: MomentsInfo let onLike: () -> Void var body: some View { VStack(alignment: .leading, spacing: 12) { // 用户信息 HStack { AsyncImage(url: URL(string: moment.avatar)) { image in image .resizable() .aspectRatio(contentMode: .fill) } placeholder: { Circle() .fill(Color.gray.opacity(0.3)) } .frame(width: 40, height: 40) .clipShape(Circle()) VStack(alignment: .leading) { Text(moment.nick) .font(.headline) Text(moment.publishTime) .font(.caption) .foregroundColor(.secondary) } Spacer() } // 动态内容 if !moment.content.isEmpty { Text(moment.content) .font(.body) } // 图片内容 if let pictures = moment.dynamicResList, !pictures.isEmpty { LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 3)) { ForEach(pictures.indices, id: \.self) { index in AsyncImage(url: URL(string: pictures[index].resUrl)) { image in image .resizable() .aspectRatio(contentMode: .fill) } placeholder: { Rectangle() .fill(Color.gray.opacity(0.3)) } .frame(height: 100) .clipped() } } } // 操作栏 HStack { Button(action: onLike) { HStack { Image(systemName: moment.isLike ? "heart.fill" : "heart") .foregroundColor(moment.isLike ? .red : .gray) Text(moment.likeCount) .foregroundColor(.gray) } } Spacer() HStack { Image(systemName: "message") .foregroundColor(.gray) Text(moment.commentCount) .foregroundColor(.gray) } } } .padding() .background(Color(.systemBackground)) .cornerRadius(8) } } ``` ## **使用说明** ### **基本用法** ```swift let viewModel = MomentsLatestViewModel() // 加载最新数据 viewModel.loadLatestMoments() // 加载更多数据 viewModel.loadMoreMoments() ``` ### **分页逻辑** - 首次请求:`dynamicId` 传空字符串 - 后续分页:使用上次响应中的 `nextDynamicId` - 无更多数据:返回的 `dynamicList` 为空数组 ### **错误处理** - 网络错误:检查网络连接 - 401 认证失败:重新登录获取 ticket - 其他服务器错误:显示具体错误信息 ### **性能优化建议** 1. 使用图片缓存库(如 Kingfisher) 2. 实现虚拟列表避免内存过载 3. 预加载下一页数据提升用户体验 4. 实现本地缓存减少网络请求 ## **注意事项** 1. **认证要求**:所有请求必须包含有效的 `pub_uid` 和 `pub_ticket` 2. **参数验证**:`pageSize` 建议范围为 10-50 3. **类型过滤**:`types` 参数支持多选,用逗号分隔 4. **数据更新**:推荐使用下拉刷新获取最新数据 5. **错误重试**:网络错误时实现自动重试机制