1556 lines
42 KiB
JavaScript
1556 lines
42 KiB
JavaScript
// 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;
|
||
|