592 lines
15 KiB
JavaScript
592 lines
15 KiB
JavaScript
// 实时通知管理器 - 微信小程序专用
|
||
// 处理WebSocket消息、本地通知、订阅消息等
|
||
|
||
const wsManager = require('./websocket-manager-v2.js');
|
||
|
||
/**
|
||
* 微信小程序实时通知管理器
|
||
* 功能:
|
||
* 1. WebSocket消息处理和分发
|
||
* 2. 本地通知提醒
|
||
* 3. 订阅消息管理
|
||
* 4. 消息状态同步
|
||
* 5. 离线消息处理
|
||
*/
|
||
class NotificationManager {
|
||
constructor() {
|
||
this.isInitialized = false;
|
||
this.messageHandlers = new Map();
|
||
this.notificationQueue = [];
|
||
this.unreadCounts = {
|
||
messages: 0,
|
||
friends: 0,
|
||
system: 0
|
||
};
|
||
|
||
// 订阅消息模板ID(需要在微信公众平台配置)
|
||
this.subscribeTemplates = {
|
||
newMessage: 'template_id_for_new_message',
|
||
friendRequest: 'template_id_for_friend_request',
|
||
systemNotice: 'template_id_for_system_notice'
|
||
};
|
||
|
||
// 通知设置
|
||
this.notificationSettings = {
|
||
sound: true,
|
||
vibrate: true,
|
||
showBadge: true,
|
||
quietHours: {
|
||
enabled: false,
|
||
start: '22:00',
|
||
end: '08:00'
|
||
}
|
||
};
|
||
|
||
this.init();
|
||
}
|
||
|
||
// 初始化通知管理器
|
||
async init() {
|
||
if (this.isInitialized) return;
|
||
|
||
console.log('🔔 初始化通知管理器...');
|
||
|
||
try {
|
||
// 加载通知设置
|
||
await this.loadNotificationSettings();
|
||
|
||
// 注册WebSocket消息处理器
|
||
this.registerWebSocketHandlers();
|
||
|
||
// 注册小程序生命周期事件
|
||
this.registerAppLifecycleEvents();
|
||
|
||
// 初始化未读计数
|
||
await this.loadUnreadCounts();
|
||
|
||
this.isInitialized = true;
|
||
console.log('✅ 通知管理器初始化完成');
|
||
|
||
} catch (error) {
|
||
console.error('❌ 通知管理器初始化失败:', error);
|
||
}
|
||
}
|
||
|
||
// 注册WebSocket消息处理器
|
||
registerWebSocketHandlers() {
|
||
// 新消息通知
|
||
wsManager.on('message', (data) => {
|
||
this.handleWebSocketMessage(data);
|
||
});
|
||
|
||
// 连接状态变化
|
||
wsManager.on('connected', () => {
|
||
console.log('🔔 WebSocket连接成功,开始接收通知');
|
||
this.syncOfflineMessages();
|
||
});
|
||
|
||
wsManager.on('disconnected', () => {
|
||
console.log('🔔 WebSocket连接断开,切换到离线模式');
|
||
});
|
||
}
|
||
|
||
// 处理WebSocket消息
|
||
async handleWebSocketMessage(data) {
|
||
try {
|
||
const message = typeof data === 'string' ? JSON.parse(data) : data;
|
||
console.log('📨 收到WebSocket消息:', message.type);
|
||
|
||
switch (message.type) {
|
||
case 'new_message':
|
||
await this.handleNewMessage(message.data);
|
||
break;
|
||
case 'friend_request':
|
||
await this.handleFriendRequest(message.data);
|
||
break;
|
||
case 'friend_accepted':
|
||
await this.handleFriendAccepted(message.data);
|
||
break;
|
||
case 'system_notice':
|
||
await this.handleSystemNotice(message.data);
|
||
break;
|
||
case 'message_read':
|
||
await this.handleMessageRead(message.data);
|
||
break;
|
||
case 'user_online':
|
||
await this.handleUserOnline(message.data);
|
||
break;
|
||
case 'user_offline':
|
||
await this.handleUserOffline(message.data);
|
||
break;
|
||
default:
|
||
console.log('🔔 未知消息类型:', message.type);
|
||
}
|
||
|
||
// 触发自定义事件
|
||
this.triggerEvent('message_received', message);
|
||
|
||
} catch (error) {
|
||
console.error('❌ 处理WebSocket消息失败:', error);
|
||
}
|
||
}
|
||
|
||
// 处理新消息
|
||
async handleNewMessage(messageData) {
|
||
console.log('💬 收到新消息:', messageData);
|
||
|
||
// 更新未读计数
|
||
this.unreadCounts.messages++;
|
||
await this.saveUnreadCounts();
|
||
|
||
// 显示通知
|
||
await this.showNotification({
|
||
type: 'new_message',
|
||
title: messageData.senderName || '新消息',
|
||
content: this.formatMessageContent(messageData),
|
||
data: messageData
|
||
});
|
||
|
||
// 触发页面更新
|
||
this.triggerEvent('new_message', messageData);
|
||
|
||
// 更新tabBar徽章
|
||
this.updateTabBarBadge();
|
||
}
|
||
|
||
// 处理好友请求
|
||
async handleFriendRequest(requestData) {
|
||
console.log('👥 收到好友请求:', requestData);
|
||
|
||
// 更新未读计数
|
||
this.unreadCounts.friends++;
|
||
await this.saveUnreadCounts();
|
||
|
||
// 显示通知
|
||
await this.showNotification({
|
||
type: 'friend_request',
|
||
title: '新的好友请求',
|
||
content: `${requestData.senderName} 请求添加您为好友`,
|
||
data: requestData
|
||
});
|
||
|
||
// 触发页面更新
|
||
this.triggerEvent('friend_request', requestData);
|
||
|
||
// 更新tabBar徽章
|
||
this.updateTabBarBadge();
|
||
}
|
||
|
||
// 处理好友请求被接受
|
||
async handleFriendAccepted(acceptData) {
|
||
console.log('✅ 好友请求被接受:', acceptData);
|
||
|
||
// 显示通知
|
||
await this.showNotification({
|
||
type: 'friend_accepted',
|
||
title: '好友请求已接受',
|
||
content: `${acceptData.friendName} 已接受您的好友请求`,
|
||
data: acceptData
|
||
});
|
||
|
||
// 触发页面更新
|
||
this.triggerEvent('friend_accepted', acceptData);
|
||
}
|
||
|
||
// 处理系统通知
|
||
async handleSystemNotice(noticeData) {
|
||
console.log('📢 收到系统通知:', noticeData);
|
||
|
||
// 更新未读计数
|
||
this.unreadCounts.system++;
|
||
await this.saveUnreadCounts();
|
||
|
||
// 显示通知
|
||
await this.showNotification({
|
||
type: 'system_notice',
|
||
title: '系统通知',
|
||
content: noticeData.content,
|
||
data: noticeData
|
||
});
|
||
|
||
// 触发页面更新
|
||
this.triggerEvent('system_notice', noticeData);
|
||
}
|
||
|
||
// 处理消息已读
|
||
async handleMessageRead(readData) {
|
||
console.log('👁️ 消息已读:', readData);
|
||
|
||
// 更新未读计数
|
||
if (readData.count && this.unreadCounts.messages >= readData.count) {
|
||
this.unreadCounts.messages -= readData.count;
|
||
await this.saveUnreadCounts();
|
||
this.updateTabBarBadge();
|
||
}
|
||
|
||
// 触发页面更新
|
||
this.triggerEvent('message_read', readData);
|
||
}
|
||
|
||
// 显示通知
|
||
async showNotification(notification) {
|
||
try {
|
||
// 检查是否在静默时间
|
||
if (this.isInQuietHours()) {
|
||
console.log('🔇 当前为静默时间,跳过通知');
|
||
return;
|
||
}
|
||
|
||
// 检查应用状态
|
||
const appState = this.getAppState();
|
||
|
||
if (appState === 'foreground') {
|
||
// 前台显示本地通知
|
||
await this.showLocalNotification(notification);
|
||
} else {
|
||
// 后台尝试发送订阅消息
|
||
await this.sendSubscribeMessage(notification);
|
||
}
|
||
|
||
// 播放提示音
|
||
if (this.notificationSettings.sound) {
|
||
this.playNotificationSound();
|
||
}
|
||
|
||
// 震动提醒
|
||
if (this.notificationSettings.vibrate) {
|
||
this.vibrateDevice();
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('❌ 显示通知失败:', error);
|
||
}
|
||
}
|
||
|
||
// 显示本地通知(前台)
|
||
async showLocalNotification(notification) {
|
||
// 微信小程序没有原生的本地通知API
|
||
// 这里使用自定义的通知组件或Toast
|
||
|
||
wx.showToast({
|
||
title: notification.content,
|
||
icon: 'none',
|
||
duration: 3000
|
||
});
|
||
|
||
// 如果有自定义通知组件,可以在这里调用
|
||
// this.triggerEvent('show_custom_notification', notification);
|
||
}
|
||
|
||
// 发送订阅消息(后台)
|
||
async sendSubscribeMessage(notification) {
|
||
try {
|
||
// 检查是否有订阅权限
|
||
const templateId = this.getTemplateId(notification.type);
|
||
if (!templateId) {
|
||
console.log('🔔 没有对应的订阅消息模板');
|
||
return;
|
||
}
|
||
|
||
// 这里需要调用后端API发送订阅消息
|
||
// 因为订阅消息需要在服务端发送
|
||
console.log('📤 请求发送订阅消息:', {
|
||
templateId,
|
||
notification
|
||
});
|
||
|
||
// TODO: 调用后端API发送订阅消息
|
||
|
||
} catch (error) {
|
||
console.error('❌ 发送订阅消息失败:', error);
|
||
}
|
||
}
|
||
|
||
// 格式化消息内容
|
||
formatMessageContent(messageData) {
|
||
switch (messageData.msgType) {
|
||
case 0: // 文本消息
|
||
return messageData.content;
|
||
case 1: // 图片消息
|
||
return '[图片]';
|
||
case 2: // 语音消息
|
||
return '[语音]';
|
||
case 3: // 视频消息
|
||
return '[视频]';
|
||
case 4: // 文件消息
|
||
return '[文件]';
|
||
default:
|
||
return '[消息]';
|
||
}
|
||
}
|
||
|
||
// 获取模板ID
|
||
getTemplateId(notificationType) {
|
||
return this.subscribeTemplates[notificationType];
|
||
}
|
||
|
||
// 检查是否在静默时间
|
||
isInQuietHours() {
|
||
if (!this.notificationSettings.quietHours.enabled) {
|
||
return false;
|
||
}
|
||
|
||
const now = new Date();
|
||
const currentTime = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;
|
||
const { start, end } = this.notificationSettings.quietHours;
|
||
|
||
if (start <= end) {
|
||
return currentTime >= start && currentTime <= end;
|
||
} else {
|
||
return currentTime >= start || currentTime <= end;
|
||
}
|
||
}
|
||
|
||
// 获取应用状态
|
||
getAppState() {
|
||
// 微信小程序中可以通过页面栈判断应用状态
|
||
const pages = getCurrentPages();
|
||
return pages.length > 0 ? 'foreground' : 'background';
|
||
}
|
||
|
||
// 播放提示音
|
||
playNotificationSound() {
|
||
try {
|
||
// 微信小程序播放系统提示音
|
||
wx.createInnerAudioContext().play();
|
||
} catch (error) {
|
||
console.error('❌ 播放提示音失败:', error);
|
||
}
|
||
}
|
||
|
||
// 震动设备
|
||
vibrateDevice() {
|
||
try {
|
||
wx.vibrateShort({
|
||
type: 'medium'
|
||
});
|
||
} catch (error) {
|
||
console.error('❌ 震动失败:', error);
|
||
}
|
||
}
|
||
|
||
// 更新tabBar徽章
|
||
updateTabBarBadge() {
|
||
try {
|
||
const totalUnread = this.unreadCounts.messages + this.unreadCounts.friends + this.unreadCounts.system;
|
||
|
||
if (totalUnread > 0) {
|
||
wx.setTabBarBadge({
|
||
index: 1, // 消息页面的索引
|
||
text: totalUnread > 99 ? '99+' : totalUnread.toString()
|
||
});
|
||
} else {
|
||
wx.removeTabBarBadge({
|
||
index: 1
|
||
});
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ 更新tabBar徽章失败:', error);
|
||
}
|
||
}
|
||
|
||
// 同步离线消息
|
||
async syncOfflineMessages() {
|
||
try {
|
||
console.log('🔄 同步离线消息...');
|
||
|
||
// 获取最后同步时间
|
||
const lastSyncTime = wx.getStorageSync('lastMessageSyncTime') || 0;
|
||
|
||
// 调用API获取离线消息
|
||
const apiClient = require('./api-client.js');
|
||
const response = await apiClient.request({
|
||
url: '/api/v1/messages/offline',
|
||
method: 'GET',
|
||
data: {
|
||
since: lastSyncTime
|
||
}
|
||
});
|
||
|
||
if (response.success && response.data.messages) {
|
||
console.log(`📥 收到 ${response.data.messages.length} 条离线消息`);
|
||
|
||
// 处理离线消息
|
||
for (const message of response.data.messages) {
|
||
await this.handleNewMessage(message);
|
||
}
|
||
|
||
// 更新同步时间
|
||
wx.setStorageSync('lastMessageSyncTime', Date.now());
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('❌ 同步离线消息失败:', error);
|
||
}
|
||
}
|
||
|
||
// 注册小程序生命周期事件
|
||
registerAppLifecycleEvents() {
|
||
// 监听小程序显示
|
||
wx.onAppShow(() => {
|
||
console.log('🔔 小程序显示,检查未读消息');
|
||
this.syncOfflineMessages();
|
||
});
|
||
|
||
// 监听小程序隐藏
|
||
wx.onAppHide(() => {
|
||
console.log('🔔 小程序隐藏,保存状态');
|
||
this.saveUnreadCounts();
|
||
});
|
||
}
|
||
|
||
// 加载通知设置
|
||
async loadNotificationSettings() {
|
||
try {
|
||
const settings = wx.getStorageSync('notificationSettings');
|
||
if (settings) {
|
||
this.notificationSettings = { ...this.notificationSettings, ...settings };
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ 加载通知设置失败:', error);
|
||
}
|
||
}
|
||
|
||
// 保存通知设置
|
||
async saveNotificationSettings() {
|
||
try {
|
||
wx.setStorageSync('notificationSettings', this.notificationSettings);
|
||
} catch (error) {
|
||
console.error('❌ 保存通知设置失败:', error);
|
||
}
|
||
}
|
||
|
||
// 加载未读计数
|
||
async loadUnreadCounts() {
|
||
try {
|
||
const counts = wx.getStorageSync('unreadCounts');
|
||
if (counts) {
|
||
this.unreadCounts = { ...this.unreadCounts, ...counts };
|
||
this.updateTabBarBadge();
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ 加载未读计数失败:', error);
|
||
}
|
||
}
|
||
|
||
// 保存未读计数
|
||
async saveUnreadCounts() {
|
||
try {
|
||
wx.setStorageSync('unreadCounts', this.unreadCounts);
|
||
} catch (error) {
|
||
console.error('❌ 保存未读计数失败:', error);
|
||
}
|
||
}
|
||
|
||
// 清除未读计数
|
||
async clearUnreadCount(type) {
|
||
if (this.unreadCounts[type] !== undefined) {
|
||
this.unreadCounts[type] = 0;
|
||
await this.saveUnreadCounts();
|
||
this.updateTabBarBadge();
|
||
}
|
||
}
|
||
|
||
// 获取未读计数
|
||
getUnreadCount(type) {
|
||
return this.unreadCounts[type] || 0;
|
||
}
|
||
|
||
// 获取总未读计数
|
||
getTotalUnreadCount() {
|
||
return Object.values(this.unreadCounts).reduce((total, count) => total + count, 0);
|
||
}
|
||
|
||
// 更新通知设置
|
||
updateNotificationSettings(settings) {
|
||
this.notificationSettings = { ...this.notificationSettings, ...settings };
|
||
this.saveNotificationSettings();
|
||
}
|
||
|
||
// 请求订阅消息权限
|
||
async requestSubscribeMessage(templateIds) {
|
||
try {
|
||
const result = await new Promise((resolve, reject) => {
|
||
wx.requestSubscribeMessage({
|
||
tmplIds: Array.isArray(templateIds) ? templateIds : [templateIds],
|
||
success: resolve,
|
||
fail: reject
|
||
});
|
||
});
|
||
|
||
console.log('📝 订阅消息权限请求结果:', result);
|
||
return result;
|
||
|
||
} catch (error) {
|
||
console.error('❌ 请求订阅消息权限失败:', error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// 事件处理器
|
||
eventHandlers = new Map();
|
||
|
||
// 注册事件监听器
|
||
on(event, handler) {
|
||
if (!this.eventHandlers.has(event)) {
|
||
this.eventHandlers.set(event, []);
|
||
}
|
||
this.eventHandlers.get(event).push(handler);
|
||
}
|
||
|
||
// 移除事件监听器
|
||
off(event, handler) {
|
||
const handlers = this.eventHandlers.get(event);
|
||
if (handlers) {
|
||
const index = handlers.indexOf(handler);
|
||
if (index > -1) {
|
||
handlers.splice(index, 1);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 触发事件
|
||
triggerEvent(event, data) {
|
||
const handlers = this.eventHandlers.get(event);
|
||
if (handlers) {
|
||
handlers.forEach(handler => {
|
||
try {
|
||
handler(data);
|
||
} catch (error) {
|
||
console.error(`❌ 事件处理器错误 [${event}]:`, error);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
// 获取通知管理器状态
|
||
getStatus() {
|
||
return {
|
||
isInitialized: this.isInitialized,
|
||
unreadCounts: { ...this.unreadCounts },
|
||
notificationSettings: { ...this.notificationSettings },
|
||
totalUnread: this.getTotalUnreadCount()
|
||
};
|
||
}
|
||
|
||
// 重置通知管理器
|
||
reset() {
|
||
this.unreadCounts = {
|
||
messages: 0,
|
||
friends: 0,
|
||
system: 0
|
||
};
|
||
this.saveUnreadCounts();
|
||
this.updateTabBarBadge();
|
||
this.eventHandlers.clear();
|
||
}
|
||
}
|
||
|
||
// 创建全局实例
|
||
const notificationManager = new NotificationManager();
|
||
|
||
module.exports = notificationManager;
|