findme-miniprogram-frontend/utils/api-client.js
2025-12-27 17:16:03 +08:00

1556 lines
42 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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;