miniprogramme/utils/api-client.js

1091 lines
30 KiB
JavaScript
Raw Normal View History

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