// API客户端工具类 - 真实API版本 const config = require('../config/config.js'); const imageCacheManager = require('./image-cache-manager.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; } } catch (error) { console.error('初始化时获取token失败:', error); } // 请求拦截器 this.requestInterceptors = []; this.responseInterceptors = []; } /** * 🔥 统一处理401未授权错误 * @param {boolean} isLoggedIn - 用户是否已登录 * @param {string} message - 错误消息(可选) */ handle401Error(isLoggedIn = false, message = '登录已过期,请重新登录') { const app = getApp(); // 清除所有登录相关的本地存储 try { wx.removeStorageSync('user_token'); wx.removeStorageSync('userInfo'); wx.removeStorageSync('token'); } catch (error) { console.error('清除存储数据失败:', error); } // 清除全局登录状态 if (app) { app.globalData.isLoggedIn = false; app.globalData.userInfo = null; app.globalData.nim = null; } // 在UI线程中执行跳转和提示 setTimeout(() => { try { // 只对已登录用户显示登录过期提示并跳转 if (isLoggedIn) { wx.showToast({ title: message, icon: 'none', duration: 2000 }); // 延迟跳转,让用户看到提示 setTimeout(() => { wx.reLaunch({ url: '/pages/login/login' }); }, 500); } // 未登录用户不跳转,保持在当前页面 } catch (error) { console.error('处理401错误时出错:', error); // 只对已登录用户跳转 if (isLoggedIn) { wx.reLaunch({ url: '/pages/login/login' }); } } }, 0); } /** * 🔥 检查响应是否为401错误 * @param {Object} res - 响应对象 * @returns {boolean} 是否为401错误 */ is401Error(res) { // 检查HTTP状态码 if (res.statusCode === 401) { return true; } // 检查业务状态码 if (res.data && typeof res.data === 'object' && res.data.code === 401) { return true; } return false; } // 设置token setToken(token) { this.token = token; } // 清除token clearToken() { this.token = null; } // 获取token getToken() { // 如果没有token,尝试从存储中获取 if (!this.token) { try { const userInfo = wx.getStorageSync('userInfo'); if (userInfo && userInfo.token) { this.token = userInfo.token; } else { } } catch (error) { console.error('从存储获取token失败:', error); } } return this.token; } // 🔄 UPDATE your uploadAvatar method to clear cache async uploadAvatar(filePath) { try { // wx.showLoading({ title: 'Uploading avatar...' }); // Get old avatar URL before upload const oldAvatarUrl = this.getCurrentAvatarUrl(); // Step 1: Upload file to server (using your existing method) const uploadResult = await this.uploadFile(filePath, 'image', 'avatar'); // 获取文件数据:uploadResult.data 是响应数据,文件信息在 data.data 中 const fileData = uploadResult?.data?.data || uploadResult?.data || {}; // 尝试多种可能的字段名 debugger; const serverUrl = fileData.file_url || fileData.fileUrl || fileData.url || fileData.avatar || fileData.avatarUrl; if (!serverUrl) { console.error('上传返回数据:', uploadResult); console.error('文件数据:', fileData); throw new Error('Upload failed: No file URL received from server'); } // Step 2: Update user profile with new avatar URL (using your existing method) const profileUpdateResult = await this.updateUserProfile({ avatar: serverUrl }); // Step 3: 🔥 更新缓存 - 使用全局缓存管理器 const cachedAvatarUrl = await imageCacheManager.updateAvatarCache( oldAvatarUrl, serverUrl ); // Step 4: Refresh local user info cache const refreshedUserInfo = await this.refreshUserInfoCache(); // Step 5: Notify all pages about the avatar update with cached URL this.notifyAvatarUpdate({ serverUrl, cachedUrl: cachedAvatarUrl, userInfo: refreshedUserInfo }); wx.hideLoading(); return { success: true, fileUrl: serverUrl, cachedUrl: cachedAvatarUrl, userInfo: refreshedUserInfo, uploadData: fileData }; } catch (error) { wx.hideLoading(); console.error('Complete avatar upload flow failed:', error); // Show user-friendly error message const errorMessage = this.getAvatarUploadErrorMessage(error); wx.showToast({ title: errorMessage, icon: 'error', duration: 3000 }); throw error; } } // 刷新用户信息缓存 async refreshUserInfoCache() { try { const response = await this.getUserInfo(); const profile = response?.data || null; const storedAuth = wx.getStorageSync('userInfo') || {}; const updatedInfo = Object.assign({}, storedAuth, { user: profile || storedAuth.user }); // 写回全局和本地缓存 wx.setStorageSync('userInfo', updatedInfo); if (profile?.avatar) { wx.setStorageSync('latestAvatarUrl', profile.avatar); } else if (storedAuth?.user?.avatar) { wx.setStorageSync('latestAvatarUrl', storedAuth.user.avatar); } else { try { wx.removeStorageSync('latestAvatarUrl'); } catch (e) { console.warn('移除latestAvatarUrl失败:', e); } } const app = getApp(); if (app) { app.globalData.userInfo = updatedInfo; if (updatedInfo.token) { app.globalData.isLoggedIn = true; } } return updatedInfo; } catch (error) { console.error('刷新用户信息缓存失败:', error); return wx.getStorageSync('userInfo') || null; } } // 通知页面头像已更新 notifyAvatarUpdate({ serverUrl, cachedUrl, userInfo }) { try { const app = getApp(); if (app && typeof app.updateUserAvatar === 'function') { app.updateUserAvatar(serverUrl, { cachedUrl, userInfo }); return; } // 回退处理:直接更新全局数据 const latestInfo = userInfo || wx.getStorageSync('userInfo') || {}; if (latestInfo && latestInfo.user) { latestInfo.user.avatar = serverUrl; wx.setStorageSync('userInfo', latestInfo); } const pages = getCurrentPages(); pages.forEach(page => { if (typeof page.onAvatarUpdated === 'function') { page.onAvatarUpdated({ avatarUrl: serverUrl, cachedUrl, userInfo: latestInfo }); } else if (page.setData && page.data?.userInfo?.user) { page.setData({ 'userInfo.user.avatar': cachedUrl || serverUrl }); } }); } catch (error) { console.error('通知头像更新失败:', error); } } // 头像上传错误文案统一处理 getAvatarUploadErrorMessage(error) { if (!error) { return '头像上传失败,请稍后重试'; } if (typeof error === 'string') { return error; } if (error.message) { if (error.message.includes('timeout')) { return '上传超时,请检查网络后重试'; } if (error.message.includes('network') || error.message.includes('网络')) { return '网络异常,上传失败'; } return error.message; } if (error.errMsg) { if (error.errMsg.includes('cancel')) { return '已取消上传'; } if (error.errMsg.includes('timeout')) { return '上传超时,请稍后重试'; } return error.errMsg.replace('wx.uploadFile:fail ', ''); } return '头像上传失败,请稍后再试'; } // 🔄 UPDATE getCurrentAvatarUrl method to check cache getCurrentAvatarUrl() { try { // Try storage first const latestUrl = wx.getStorageSync('latestAvatarUrl'); if (latestUrl) return latestUrl; // Check user info for original URL const userInfo = wx.getStorageSync('userInfo'); if (userInfo && userInfo.user && userInfo.user.avatar) { // Return cached version if available const originalUrl = userInfo.user.avatar; if (imageCacheManager.isCached(originalUrl, 'avatar')) { return imageCacheManager.getCachedPath(originalUrl, 'avatar'); } return originalUrl; } // Fall back to global data const app = getApp(); if (app && app.globalData && app.globalData.userAvatar) { return app.globalData.userAvatar; } return null; } catch (error) { console.error('Failed to get current avatar URL:', error); return null; } } // 获取设备信息 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}`; 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) => { // 🔥 统一处理401未授权情况 if (this.is401Error(res)) { const app = getApp(); const isLoggedIn = app?.globalData?.isLoggedIn || false; // 使用统一的401处理函数 this.handle401Error(isLoggedIn); // 只对已登录用户抛出错误 if (isLoggedIn) { reject(new Error('登录已过期')); } else { // 对未登录用户返回空数据,不抛出错误 resolve(null); } return; } 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); // } resolve(res.data); } else { const error= new Error(`HTTP ${res.statusCode}: ${res.data?.message || 'request failed'}`); error.code=(res.data?.code)?(res.data?.code):error.code error.message=(res.data?.message)?(res.data?.message):error.message reject(error); } }, 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 }); // 不在这里保存数据,让认证管理器统一处理 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) { // 检查是否包含手机号code(新的快捷登录方式) if (userInfo.phoneCode) { loginData.phoneCode = userInfo.phoneCode; } else { // 传统方式,保持兼容 loginData.userInfo = userInfo; } } const response = await this.post('/api/v1/user/wechat-login', loginData); // 不在这里保存数据,让认证管理器统一处理 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; } } // 获取指定用户信息(通过 customId) async getUserInfoByCustomId(customId) { try { const response = await this.get(`/api/v1/user/info/${customId}`); return response; } catch (error) { console.error('获取指定用户信息失败:', error); throw error; } } // 点赞动态 async addLikeDynamic(feedUuid, avatar) { try { const response = await this.post(`/api/v1/feeds/${feedUuid}`+'/like', { avatar: avatar }); return response; } catch (error) { console.error('点赞失败:', error); return error; } } //取消点赞 async deleteLikeDynamic(feedUuid) { try { const response = await this.delete(`/api/v1/feeds/${feedUuid}`+'/like'); return response; } catch (error) { console.error('点赞失败:', error); return error; } } // 评论动态 async addCommentDynamic(feedUuid,contentComment,replyToId,nickname) { try { const response = await this.post(`/api/v1/feeds/${feedUuid}`+'/comments',{content:contentComment,FeedUUID:feedUuid,replyToId,nickname}); return response; } catch (error) { console.error('评论失败:', error); return error; } } // 删除动态 async deleteCommentDynamic(feedUuid,commentId) { try { const response = await this.delete(`/api/v1/feeds/${feedUuid}`+'/comments/'+commentId); return response; } catch (error) { console.error('删除失败:', error); return 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 getFriendsAndStranger(params) { try { const response = await this.post('/api/v1/location/map-display',params); return response; } catch (error) { console.error('获取好友陌生人位置失败:', error); throw error; } } // 获取好友和陌生人位置新接口 async getExpandCluster(params) { try { const response = await this.post('/api/v1/location/expand-cluster',params); 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; } } // 地图动态 - 街道级 async getMapFeedsNearby(payload = {}) { try { const response = await this.post('/api/v1/feeds/map/nearby', payload); return response; } catch (error) { console.error('获取街道级地图动态失败:', error); throw error; } } // 地图动态 - 行政级 async getMapFeedsAdministrative(payload = {}) { try { const response = await this.post('/api/v1/feeds/map/administrative', payload); return response; } catch (error) { console.error('获取行政级地图动态失败:', error); throw error; } } // 地图动态 - 聚合详情 async getMapClusterFeeds(payload = {}) { try { const response = await this.post('/api/v1/feeds/cluster', payload); return response; } catch (error) { console.error('获取聚合点动态失败:', error); throw error; } } async getFeedsDetail(feedUUid) { try { const response = await this.get('/api/v1/feeds/'+feedUUid); 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; } } // - nickname: 2-20个字符,必填 // - bio: 最大200个字符,可选 // - gender: 0-未知,1-男,2-女,3-其他 // - birthday: YYYY-MM-DD格式,可选 // - ethnicity: 最大50个字符,可选 // 更新好友关系 async updateUserProFileInfo(nickname, gender, birthday, avatar, zodiacSign) { try { if (!nickname || nickname.length <2 || nickname.length > 20) { throw new Error('用户昵称必须填写,昵称长度必须在2~20个字符'); } if (!(gender==0 || gender == 1 || gender == 2)) { gender=3 } // 构建请求数据 const requestData = { nickname, gender, birthday: birthday || undefined, avatar: avatar || undefined, zodiacSign: zodiacSign || undefined }; // 移除空值字段 Object.keys(requestData).forEach(key => { if (requestData[key] === undefined || requestData[key] === null || requestData[key] === '') { delete requestData[key]; } }); const response = await this.put('/api/v1/user/profile', requestData); 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 { 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) => { try { // 🔥 统一处理401未授权情况 if (this.is401Error(res)) { const app = getApp(); const isLoggedIn = app?.globalData?.isLoggedIn || false; // 使用统一的401处理函数 this.handle401Error(isLoggedIn); reject(new Error('登录已过期')); return; } 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(); return true; } catch (error) { console.error('Token有效性测试失败:', error); return false; } } } // 创建全局实例 const apiClient = new ApiClient(); module.exports = apiClient;