findme-miniprogram-frontend/utils/api-client.js

1557 lines
42 KiB
JavaScript
Raw Normal View History

2025-12-27 17:16:03 +08:00
// 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('用户昵称必须填写昵称长度必须在220个字符');
}
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('消息发送必须使用WebSocketHTTP发送接口已废弃');
}
// 批量标记消息已读
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;