// API客户端工具类 - 真实API版本 const config = require('../config/config.js'); class ApiClient { constructor() { this.baseUrl = config.api.baseUrl; this.timeout = config.api.timeout || 15000; this.token = null; // 自动从本地存储获取token try { const userInfo = wx.getStorageSync('userInfo'); if (userInfo && userInfo.token) { this.token = userInfo.token; console.log('API客户端初始化,已加载token'); } } catch (error) { console.error('初始化时获取token失败:', error); } // 请求拦截器 this.requestInterceptors = []; this.responseInterceptors = []; console.log('API客户端初始化:', { baseUrl: this.baseUrl, timeout: this.timeout, configLoaded: !!config?.api }); } // 设置token setToken(token) { this.token = token; console.log('Token已设置'); } // 清除token clearToken() { this.token = null; console.log('Token已清除'); } // 获取token getToken() { // 如果没有token,尝试从存储中获取 if (!this.token) { try { const userInfo = wx.getStorageSync('userInfo'); if (userInfo && userInfo.token) { this.token = userInfo.token; console.log('从存储中获取token成功,用户customId:', userInfo.user?.customId); } else { console.log('存储中没有找到有效的token'); } } catch (error) { console.error('从存储获取token失败:', error); } } return this.token; } // 获取设备信息 async getDeviceInfo() { try { // 使用新的API替代废弃的wx.getSystemInfoSync const [windowInfo, deviceInfo, appBaseInfo] = await Promise.all([ this.getWindowInfo(), this.getDeviceInfo_new(), this.getAppBaseInfo() ]); const systemInfo = { ...windowInfo, ...deviceInfo, ...appBaseInfo }; return { deviceId: deviceInfo.deviceId || systemInfo.deviceId || 'unknown', deviceModel: deviceInfo.model || systemInfo.model || 'unknown', deviceType: 'miniprogram', // 标识为小程序 appVersion: config?.appVersion || '1.0.0', platform: deviceInfo.platform || systemInfo.platform || 'unknown', system: deviceInfo.system || systemInfo.system || 'unknown' }; } catch (error) { console.error('获取设备信息失败,使用兜底方案:', error); // 兜底使用旧API try { const systemInfo = wx.getSystemInfoSync(); return { deviceId: systemInfo.deviceId || 'unknown', deviceModel: systemInfo.model || 'unknown', deviceType: 'miniprogram', appVersion: config?.appVersion || '1.0.0', platform: systemInfo.platform || 'unknown', system: systemInfo.system || 'unknown' }; } catch (fallbackError) { console.error('兜底方案也失败:', fallbackError); return { deviceId: 'unknown', deviceModel: 'unknown', deviceType: 'miniprogram', appVersion: config?.appVersion || '1.0.0', platform: 'unknown', system: 'unknown' }; } } } // 获取窗口信息 getWindowInfo() { return new Promise((resolve) => { try { const windowInfo = wx.getWindowInfo(); resolve(windowInfo); } catch (error) { resolve({}); } }); } // 获取设备信息(新API) getDeviceInfo_new() { return new Promise((resolve) => { try { const deviceInfo = wx.getDeviceInfo(); resolve(deviceInfo); } catch (error) { resolve({}); } }); } // 获取应用基础信息 getAppBaseInfo() { return new Promise((resolve) => { try { const appBaseInfo = wx.getAppBaseInfo(); resolve(appBaseInfo); } catch (error) { resolve({}); } }); } // 编码查询参数 encodeParams(params) { if (!params) return ''; return Object.keys(params) .filter(key => params[key] !== null && params[key] !== undefined) .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`) .join('&'); } // 通用请求方法 async request(method, url, data = null, options = {}) { const token = this.getToken(); const fullUrl = url.startsWith('http') ? url : `${this.baseUrl}${url}`; console.log('发起API请求:', { method, url: fullUrl, hasToken: !!token, hasData: !!data }); const requestOptions = { url: fullUrl, method: method.toUpperCase(), timeout: this.timeout, header: { 'Content-Type': 'application/json', 'X-Client-Version': `FindMe-MiniProgram/${config?.appVersion || '1.0.0'}`, ...options.headers } }; // 添加认证头 if (token) { requestOptions.header['Authorization'] = `Bearer ${token}`; } // 处理请求数据 if (data) { if (method.toUpperCase() === 'GET') { // GET请求,将数据转换为查询参数 const queryString = this.encodeParams(data); if (queryString) { requestOptions.url += (requestOptions.url.includes('?') ? '&' : '?') + queryString; } } else { // 其他请求,将数据作为请求体 requestOptions.data = data; } } return new Promise((resolve, reject) => { wx.request({ ...requestOptions, success: (res) => { console.log('API响应:', { url: fullUrl, statusCode: res.statusCode, data: res.data }); if (res.statusCode >= 200 && res.statusCode < 300) { // 检查业务状态码 if (res.data && typeof res.data === 'object') { if (res.data.code === 0 || res.data.code === 200) { resolve(res.data); } else { reject(new Error(`HTTP ${res.statusCode}: ${res.data.message || 'request:ok'}`)); } } else { resolve(res.data); } } else { reject(new Error(`HTTP ${res.statusCode}: ${res.data?.message || 'request failed'}`)); } }, fail: (error) => { console.error('API请求失败:', error); reject(new Error(error.errMsg || '网络请求失败')); } }); }); } // GET请求 async get(url, params = null, options = {}) { return this.request('GET', url, params, options); } // POST请求 async post(url, data = null, options = {}) { return this.request('POST', url, data, options); } // PUT请求 async put(url, data = null, options = {}) { return this.request('PUT', url, data, options); } // DELETE请求 async delete(url, data = null, options = {}) { return this.request('DELETE', url, data, options); } // 🔥 用户认证相关接口 // 发送验证码(生产级别实现,支持重试) async sendVerifyCode(phone, retryCount = 0) { try { const response = await this.post('/api/v1/user/send-verify-code', { phone }); return response; } catch (error) { console.error('发送验证码失败:', error); // 网络错误时自动重试(最多重试2次) if (retryCount < 2 && this.isNetworkError(error)) { console.log(`网络错误,正在重试... (${retryCount + 1}/2)`); await this.delay(1000 * (retryCount + 1)); // 递增延迟 return this.sendVerifyCode(phone, retryCount + 1); } throw error; } } // 判断是否为网络错误 isNetworkError(error) { return error.message && ( error.message.includes('网络') || error.message.includes('timeout') || error.message.includes('Network') || error.message.includes('Failed to fetch') ); } // 延迟函数 delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // 用户登录 async login(phone, verifyCode) { try { // 获取设备信息 const deviceInfo = await this.getDeviceInfo(); const response = await this.post('/api/v1/user/login', { phone, verifyCode, source: 'miniprogram', // 根据文档使用source字段(小写) deviceId: deviceInfo.deviceId, deviceType: deviceInfo.deviceType, appVersion: deviceInfo.appVersion }); // 不在这里保存数据,让认证管理器统一处理 console.log('登录API调用成功,设备信息:', deviceInfo); return response; } catch (error) { console.error('登录失败:', error); throw error; } } // 微信登录 async wechatLogin(code, userInfo = null) { try { // 获取设备信息 const deviceInfo = await this.getDeviceInfo(); const loginData = { code, source: 'miniprogram', // 根据文档使用source字段(小写) deviceId: deviceInfo.deviceId, deviceType: deviceInfo.deviceType, appVersion: deviceInfo.appVersion }; if (userInfo) { loginData.userInfo = userInfo; } const response = await this.post('/api/v1/user/wechat-login', loginData); // 不在这里保存数据,让认证管理器统一处理 console.log('微信登录API调用成功,设备信息:', deviceInfo); return response; } catch (error) { console.error('微信登录失败:', error); throw error; } } // 获取用户信息 async getUserInfo() { try { const response = await this.get('/api/v1/user/info'); return response; } catch (error) { console.error('获取用户信息失败:', error); throw error; } } // 🔥 账号同步相关接口 // 绑定手机号 async bindPhone(phone, verifyCode, autoMerge = false) { try { // 获取设备信息 const deviceInfo = await this.getDeviceInfo(); const response = await this.post('/api/v1/user/bind-phone', { phone, verifyCode, deviceId: deviceInfo.deviceId, // 根据文档保留deviceId autoMerge }); return response; } catch (error) { console.error('绑定手机号失败:', error); throw error; } } // 检测可合并账号 async detectMerge(customId, autoMerge = false) { try { const response = await this.post('/api/v1/user/detect-merge', { userCustomId: customId, // 根据文档使用userCustomId参数 autoMerge }); return response; } catch (error) { console.error('检测可合并账号失败:', error); throw error; } } // 合并账号 async mergeAccount(primaryCustomId, secondaryCustomId, mergeReason = '用户手动合并') { try { const response = await this.post('/api/v1/user/merge-account', { primaryUserCustomId: primaryCustomId, // 根据文档使用primaryUserCustomId参数 secondaryUserCustomId: secondaryCustomId, // 根据文档使用secondaryUserCustomId参数 mergeReason }); return response; } catch (error) { console.error('合并账号失败:', error); throw error; } } // 更新用户资料 async updateUserProfile(data) { try { const response = await this.put('/api/v1/user/profile', data); return response; } catch (error) { console.error('更新用户资料失败:', error); throw error; } } // 获取用户设置 async getUserSetting() { try { const response = await this.get('/api/v1/user/setting'); return response; } catch (error) { console.error('获取用户设置失败:', error); throw error; } } // 刷新token async refreshToken(refreshToken) { try { const response = await this.post('/api/v1/auth/refresh', { refresh_token: refreshToken }); if (response && response.code === 200 && response.data) { this.setToken(response.data.access_token); // 更新本地存储 - 保持字段名一致性 const userInfo = wx.getStorageSync('userInfo') || {}; userInfo.token = response.data.access_token; userInfo.refreshToken = response.data.refresh_token; // 保持一致的字段名 userInfo.expiresAt = response.data.expires_at * 1000; // 转换为毫秒时间戳 wx.setStorageSync('userInfo', userInfo); } return response; } catch (error) { console.error('刷新token失败:', error); throw error; } } // 用户登出 async logout() { try { const response = await this.post('/api/v1/user/logout'); // 清除本地token和用户信息 this.clearToken(); wx.removeStorageSync('userInfo'); return response; } catch (error) { console.error('登出失败:', error); // 即使登出失败,也清除本地信息 this.clearToken(); wx.removeStorageSync('userInfo'); throw error; } } // 🔥 位置相关接口 // 更新位置 async updateLocation(locationData) { try { const response = await this.post('/api/v1/location/update', locationData); return response; } catch (error) { console.error('更新位置失败:', error); throw error; } } // 获取好友位置 async getFriendsLocation() { try { const response = await this.get('/api/v1/location/friends'); return response; } catch (error) { console.error('获取好友位置失败:', error); throw error; } } // 获取用户位置 async getUserLocation(userId) { try { const response = await this.get(`/api/v1/location/user/${userId}`); return response; } catch (error) { console.error('获取用户位置失败:', error); throw error; } } // 获取附近用户 async getNearbyUsers(params = {}) { try { const response = await this.get('/api/v1/location/nearby', params); return response; } catch (error) { console.error('获取附近用户失败:', error); throw error; } } // 获取位置历史 async getLocationHistory(params = {}) { try { const response = await this.get('/api/v1/location/history', params); return response; } catch (error) { console.error('获取位置历史失败:', error); throw error; } } // 获取位置隐私设置 async getLocationPrivacy() { try { const response = await this.get('/api/v1/location/privacy'); return response; } catch (error) { console.error('获取位置隐私设置失败:', error); throw error; } } // 更新位置隐私设置 async updateLocationPrivacy(privacyData) { try { const response = await this.put('/api/v1/location/privacy', privacyData); return response; } catch (error) { console.error('更新位置隐私设置失败:', error); throw error; } } // 获取天气信息 async getWeatherInfo(latitude, longitude) { try { const response = await this.get('/api/v1/location/weather', { latitude, longitude }); return response; } catch (error) { console.error('获取天气信息失败:', error); throw error; } } // 🔥 社交相关接口(根据好友功能API手册完整实现) // 获取好友列表 async getFriends() { try { const response = await this.get('/api/v1/social/friends'); return response; } catch (error) { console.error('获取好友列表失败:', error); throw error; } } // 获取好友详细信息 async getFriendDetail(customId, lat = null, lng = null) { try { let url = `/api/v1/social/friends/${customId}/detail`; const params = {}; if (lat !== null && lng !== null) { params.lat = lat; params.lng = lng; } const response = await this.get(url, params); return response; } catch (error) { console.error('获取好友详情失败:', error); throw error; } } // 搜索用户 async searchUsers(query, searchType = 'all', page = 1, pageSize = 10) { try { if (!query || typeof query !== 'string') { throw new Error('搜索关键词不能为空'); } const validSearchTypes = ['nickname', 'custom_id', 'phone', 'all']; if (!validSearchTypes.includes(searchType)) { throw new Error('无效的搜索类型'); } if (pageSize < 1 || pageSize > 50) { throw new Error('每页数量必须在1-50之间'); } const response = await this.post('/api/v1/social/users/search', { query: query.trim(), searchType: searchType, page: Math.max(1, page), pageSize: Math.min(50, pageSize) }); return response; } catch (error) { console.error('搜索用户失败:', error); throw error; } } // 添加好友 async addFriend(targetId, message = '') { try { if (!targetId || typeof targetId !== 'string') { throw new Error('目标用户ID不能为空'); } if (message && message.length > 100) { throw new Error('好友申请留言不能超过100字符'); } const response = await this.post('/api/v1/social/friend/add', { targetId: targetId, message: message.trim() }); return response; } catch (error) { console.error('添加好友失败:', error); throw error; } } // 获取好友请求列表 async getFriendRequests() { try { const response = await this.get('/api/v1/social/friend/requests'); return response; } catch (error) { console.error('获取好友请求失败:', error); throw error; } } // 获取好友请求数量 async getFriendRequestsCount() { try { const response = await this.get('/api/v1/social/friend/requests/count'); return response; } catch (error) { console.error('获取好友请求数量失败:', error); throw error; } } // 处理好友请求 async handleFriendRequest(requestId, accept) { try { if (!requestId || typeof requestId !== 'string') { throw new Error('请求ID不能为空'); } if (typeof accept !== 'boolean') { throw new Error('accept参数必须是布尔值'); } const response = await this.post('/api/v1/social/friend/handle-request', { requestId: requestId, accept: accept }); return response; } catch (error) { console.error('处理好友请求失败:', error); throw error; } } // 批量处理好友请求 async batchHandleFriendRequests(requestIds, accept) { try { if (!Array.isArray(requestIds) || requestIds.length === 0) { throw new Error('请求ID列表不能为空'); } if (requestIds.length > 20) { throw new Error('一次最多处理20个请求'); } if (typeof accept !== 'boolean') { throw new Error('accept参数必须是布尔值'); } const response = await this.post('/api/v1/social/friend/batch-handle-requests', { requestIds: requestIds, accept: accept }); return response; } catch (error) { console.error('批量处理好友请求失败:', error); throw error; } } // 更新好友关系 async updateFriendRelation(friendId, updateData) { try { if (!friendId) { throw new Error('好友ID不能为空'); } const validRelations = ['情侣', '家人', '兄弟', '姐妹', '闺蜜', '死党']; if (updateData.relation && !validRelations.includes(updateData.relation)) { throw new Error('无效的关系标签'); } if (updateData.remark && updateData.remark.length > 50) { throw new Error('好友备注不能超过50字符'); } const response = await this.put('/api/v1/social/friend', { friendId: friendId, ...updateData }); return response; } catch (error) { console.error('更新好友关系失败:', error); throw error; } } // 删除好友 async deleteFriend(friendCustomId) { try { if (!friendCustomId || typeof friendCustomId !== 'string') { throw new Error('好友CustomID不能为空'); } const response = await this.delete(`/api/v1/social/friend/${friendCustomId}`); return response; } catch (error) { console.error('删除好友失败:', error); throw error; } } // 拉取好友通知 async pullFriendNotifications(limit = 10) { try { if (limit < 1 || limit > 50) { throw new Error('拉取数量必须在1-50之间'); } const response = await this.get('/api/v1/social/friend/notifications/pull', { limit: limit }); return response; } catch (error) { console.error('拉取好友通知失败:', error); throw error; } } // 获取通知统计 async getFriendNotificationStats() { try { const response = await this.get('/api/v1/social/friend/notifications/stats'); return response; } catch (error) { console.error('获取通知统计失败:', error); throw error; } } // 获取群组列表 async getGroups() { try { const response = await this.get('/api/v1/social/groups'); return response; } catch (error) { console.error('获取群组列表失败:', error); throw error; } } // 获取群组数量 async getGroupsCount() { try { const response = await this.get('/api/v1/social/groups/count'); return response; } catch (error) { console.error('获取群组数量失败:', error); throw error; } } // 🔥 聊天相关接口(根据WebSocket即时通讯接口文档完整实现) // 获取会话列表 async getConversations() { try { const response = await this.get('/api/v1/chat/conversations'); return response; } catch (error) { console.error('获取会话列表失败:', error); throw error; } } // 获取聊天历史消息 - 根据API文档修正参数格式 async getChatMessages(targetId, chatType, params = {}) { try { const queryParams = { receiverId: targetId, // 使用receiverId而不是conversationId chatType: chatType, // 聊天类型:0=单聊, 1=群聊 limit: params.limit || 20, direction: params.direction || 'before', lastMsgId: params.lastMsgId, // 分页参数 ...params }; const response = await this.get('/api/v1/chat/history', queryParams); return response; } catch (error) { console.error('获取聊天消息失败:', error); throw error; } } // 🚫 发送消息已废弃 - 必须使用WebSocket async sendMessage(targetId, content, msgType = 'text', chatType = 0) { console.error('❌ HTTP发送消息已废弃!根据API文档,所有消息发送必须通过WebSocket'); throw new Error('消息发送必须使用WebSocket,HTTP发送接口已废弃'); } // 批量标记消息已读 async batchMarkRead(conversationId, messageIds) { try { const response = await this.post('/api/v1/chat/batch-read', { conversationId: conversationId, messageIds: messageIds }); return response; } catch (error) { console.error('批量标记已读失败:', error); throw error; } } // 全部标记已读 async markAllRead(conversationId) { try { const response = await this.post('/api/v1/chat/mark-all-read', { conversationId: conversationId }); return response; } catch (error) { console.error('全部标记已读失败:', error); throw error; } } // 获取总未读数 async getTotalUnreadCount() { try { const response = await this.get('/api/v1/chat/unread/total'); return response; } catch (error) { console.error('获取总未读数失败:', error); throw error; } } // 更新会话设置 async updateConversationSettings(conversationId, settings) { try { const response = await this.put(`/api/v1/chat/conversation/${conversationId}`, settings); return response; } catch (error) { console.error('更新会话设置失败:', error); throw error; } } // 删除会话 async deleteConversation(conversationId) { try { const response = await this.delete(`/api/v1/chat/conversation/${conversationId}`); return response; } catch (error) { console.error('删除会话失败:', error); throw error; } } // 获取聊天设置 async getChatSettings() { try { const response = await this.get('/api/v1/chat/settings'); return response; } catch (error) { console.error('获取聊天设置失败:', error); throw error; } } // 更新聊天设置 async updateChatSettings(settings) { try { const response = await this.put('/api/v1/chat/settings', settings); return response; } catch (error) { console.error('更新聊天设置失败:', error); throw error; } } // 拉取离线消息 async pullOfflineMessages(lastSeqId, limit = 50) { try { const response = await this.get('/api/v1/chat/sync/pull', { lastSeqId: lastSeqId, limit: limit }); return response; } catch (error) { console.error('拉取离线消息失败:', error); throw error; } } // 确认消息状态 async ackMessageStatus(messageId, status, timestamp) { try { const response = await this.post('/api/v1/chat/sync/ack', { messageId: messageId, status: status, timestamp: timestamp }); return response; } catch (error) { console.error('确认消息状态失败:', error); throw error; } } // 标记消息已读(兼容方法) async markMessagesRead(conversationId, messageIds) { try { if (Array.isArray(messageIds) && messageIds.length > 0) { return await this.batchMarkRead(conversationId, messageIds); } else { return await this.markAllRead(conversationId); } } catch (error) { console.error('标记消息已读失败:', error); throw error; } } // 发送虚拟弹幕 async sendDanmaku(content, color = '#FFFFFF', size = 1, duration = 5000, latitude, longitude, radius = 100) { try { if (!content || content.length > 255) { throw new Error('弹幕内容不能为空且不能超过255字符'); } const response = await this.post('/api/v1/chat/danmaku', { content: content, color: color, size: size, duration: duration, latitude: latitude, longitude: longitude, radius: radius }); return response; } catch (error) { console.error('发送弹幕失败:', error); throw error; } } // 查询附近弹幕 async getNearbyDanmaku(latitude, longitude, radius = 1000) { try { const response = await this.get('/api/v1/chat/danmaku/nearby', { latitude: latitude, longitude: longitude, radius: radius }); return response; } catch (error) { console.error('查询附近弹幕失败:', error); throw error; } } // 查询表情包 async getEmojiPackages() { try { const response = await this.get('/api/v1/chat/emoji/packages'); return response; } catch (error) { console.error('查询表情包失败:', error); throw error; } } // 🔥 文件上传接口 // 文件上传 async uploadFile(filePath, fileType = 'image', usageType = 'chat') { try { console.log('上传文件:', { filePath, fileType, usageType }); return new Promise((resolve, reject) => { wx.uploadFile({ url: `${this.baseUrl}/api/v1/file/upload`, filePath: filePath, name: 'file', formData: { file_type: fileType, usage_type: usageType }, header: { 'Authorization': this.token ? `Bearer ${this.token}` : '', 'X-Client-Version': `FindMe-MiniProgram/${config?.appVersion || '1.0.0'}` }, success: (res) => { console.log('文件上传成功:', res); try { const data = JSON.parse(res.data); if (data.code === 0) { resolve(data); } else { reject(new Error(data.message || '上传失败')); } } catch (error) { reject(new Error('响应解析失败')); } }, fail: (error) => { console.error('文件上传失败:', error); reject(new Error(error.errMsg || '上传失败')); } }); }); } catch (error) { console.error('文件上传异常:', error); throw error; } } // 🔥 测试token有效性 async testTokenValidity() { try { const token = this.getToken(); console.log('开始测试token有效性:', { hasToken: !!token, tokenLength: token ? token.length : 0, tokenStart: token ? token.substring(0, 20) + '...' : 'null' }); if (!token) { throw new Error('没有找到token'); } const response = await this.getUserInfo(); console.log('Token有效性测试成功:', response); return true; } catch (error) { console.error('Token有效性测试失败:', error); return false; } } } // 创建全局实例 const apiClient = new ApiClient(); module.exports = apiClient;