upload project
This commit is contained in:
commit
06961cae04
422 changed files with 110626 additions and 0 deletions
598
utils/notification-manager.js
Normal file
598
utils/notification-manager.js
Normal file
|
|
@ -0,0 +1,598 @@
|
|||
// 实时通知管理器 - 微信小程序专用
|
||||
// 处理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;
|
||||
|
||||
try {
|
||||
// 加载通知设置
|
||||
await this.loadNotificationSettings();
|
||||
|
||||
// 注册WebSocket消息处理器
|
||||
this.registerWebSocketHandlers();
|
||||
|
||||
// 注册小程序生命周期事件
|
||||
this.registerAppLifecycleEvents();
|
||||
|
||||
// 初始化未读计数
|
||||
await this.loadUnreadCounts();
|
||||
|
||||
this.isInitialized = true;
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 通知管理器初始化失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 注册WebSocket消息处理器
|
||||
registerWebSocketHandlers() {
|
||||
// 新消息通知
|
||||
wsManager.on('message', (data) => {
|
||||
this.handleWebSocketMessage(data);
|
||||
});
|
||||
|
||||
// 连接状态变化
|
||||
wsManager.on('connected', () => {
|
||||
|
||||
this.syncOfflineMessages();
|
||||
});
|
||||
|
||||
wsManager.on('disconnected', () => {
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
// 处理WebSocket消息
|
||||
async handleWebSocketMessage(data) {
|
||||
try {
|
||||
const message = typeof data === 'string' ? JSON.parse(data) : data;
|
||||
|
||||
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:
|
||||
|
||||
}
|
||||
|
||||
// 触发自定义事件
|
||||
this.triggerEvent('message_received', message);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 处理WebSocket消息失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理新消息
|
||||
async handleNewMessage(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) {
|
||||
|
||||
// 更新未读计数
|
||||
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) {
|
||||
|
||||
// 显示通知
|
||||
await this.showNotification({
|
||||
type: 'friend_accepted',
|
||||
title: '好友请求已接受',
|
||||
content: `${acceptData.friendName} 已接受您的好友请求`,
|
||||
data: acceptData
|
||||
});
|
||||
|
||||
// 触发页面更新
|
||||
this.triggerEvent('friend_accepted', acceptData);
|
||||
}
|
||||
|
||||
// 处理系统通知
|
||||
async handleSystemNotice(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) {
|
||||
|
||||
// 更新未读计数
|
||||
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()) {
|
||||
|
||||
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) {
|
||||
// 只对已登录用户显示通知
|
||||
const app = getApp();
|
||||
const isLoggedIn = app?.globalData?.isLoggedIn || false;
|
||||
if (!isLoggedIn) return;
|
||||
|
||||
// 微信小程序没有原生的本地通知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) {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 这里需要调用后端API发送订阅消息
|
||||
// 因为订阅消息需要在服务端发送
|
||||
|
||||
// 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;
|
||||
// 原生tabBar角标(兜底,部分机型自定义tabBar仍可用该API显示消息数)
|
||||
if (totalUnread > 0) {
|
||||
wx.setTabBarBadge({ index: 1, text: totalUnread > 99 ? '99+' : totalUnread.toString() });
|
||||
} else {
|
||||
wx.removeTabBarBadge({ index: 1 });
|
||||
}
|
||||
|
||||
// 自定义tabBar同步:优先显示各自分类的数量
|
||||
const pages = getCurrentPages();
|
||||
const cur = pages[pages.length - 1];
|
||||
if (cur && typeof cur.getTabBar === 'function') {
|
||||
const tb = cur.getTabBar && cur.getTabBar();
|
||||
if (tb && typeof tb.setFriendsBadge === 'function' && typeof tb.setMessagesBadge === 'function') {
|
||||
tb.setFriendsBadge(this.unreadCounts.friends || 0);
|
||||
tb.setMessagesBadge(this.unreadCounts.messages || 0);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 更新tabBar徽章失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 同步离线消息
|
||||
async syncOfflineMessages() {
|
||||
try {
|
||||
|
||||
// 获取最后同步时间
|
||||
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) {
|
||||
|
||||
// 处理离线消息
|
||||
for (const message of response.data.messages) {
|
||||
await this.handleNewMessage(message);
|
||||
}
|
||||
|
||||
// 更新同步时间
|
||||
wx.setStorageSync('lastMessageSyncTime', Date.now());
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 同步离线消息失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 注册小程序生命周期事件
|
||||
registerAppLifecycleEvents() {
|
||||
// 监听小程序显示
|
||||
wx.onAppShow(() => {
|
||||
|
||||
this.syncOfflineMessages();
|
||||
});
|
||||
|
||||
// 监听小程序隐藏
|
||||
wx.onAppHide(() => {
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
// 显式设置未读计数(用于以服务端真实数量为准时校准)
|
||||
async setUnreadCount(type, value) {
|
||||
if (this.unreadCounts[type] === undefined) return;
|
||||
const v = Math.max(0, Number(value) || 0);
|
||||
this.unreadCounts[type] = v;
|
||||
await this.saveUnreadCounts();
|
||||
this.updateTabBarBadge();
|
||||
}
|
||||
|
||||
async setFriendsUnreadCount(value) { return this.setUnreadCount('friends', value); }
|
||||
async setMessagesUnreadCount(value) { return this.setUnreadCount('messages', value); }
|
||||
// 获取未读计数
|
||||
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
|
||||
});
|
||||
});
|
||||
|
||||
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;
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue