/** * 分销模块WebSocket实时推送服务 * 用于推送绑定过期提醒、提现状态更新等实时消息 */ // 消息类型定义 export type WebSocketMessageType = | 'binding_expiring' // 绑定即将过期 | 'binding_expired' // 绑定已过期 | 'binding_converted' // 绑定已转化(用户付款) | 'withdrawal_approved' // 提现已通过 | 'withdrawal_completed' // 提现已完成 | 'withdrawal_rejected' // 提现已拒绝 | 'earnings_added' // 收益增加 | 'system_notice'; // 系统通知 // 消息结构 export interface WebSocketMessage { type: WebSocketMessageType; userId: string; // 目标用户ID data: Record; timestamp: string; messageId: string; } // 绑定过期提醒数据 export interface BindingExpiringData { bindingId: string; visitorNickname?: string; visitorPhone?: string; daysRemaining: number; expireTime: string; } // 提现状态更新数据 export interface WithdrawalUpdateData { withdrawalId: string; amount: number; status: string; paymentNo?: string; rejectReason?: string; } // 收益增加数据 export interface EarningsAddedData { orderId: string; orderAmount: number; commission: number; visitorNickname?: string; } /** * WebSocket消息队列(服务端存储待发送的消息) * 实际项目中应该使用Redis或其他消息队列 */ const messageQueue: Map = new Map(); /** * 生成消息ID */ function generateMessageId(): string { return `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * 添加消息到队列 */ export function pushMessage(message: Omit): void { const fullMessage: WebSocketMessage = { ...message, messageId: generateMessageId(), timestamp: new Date().toISOString(), }; const userMessages = messageQueue.get(message.userId) || []; userMessages.push(fullMessage); // 保留最近100条消息 if (userMessages.length > 100) { userMessages.shift(); } messageQueue.set(message.userId, userMessages); console.log('[WebSocket] 消息已入队:', { type: fullMessage.type, userId: fullMessage.userId, messageId: fullMessage.messageId, }); } /** * 获取用户待处理的消息 */ export function getMessages(userId: string, since?: string): WebSocketMessage[] { const userMessages = messageQueue.get(userId) || []; if (since) { return userMessages.filter(m => m.timestamp > since); } return userMessages; } /** * 清除用户已读消息 */ export function clearMessages(userId: string, messageIds: string[]): void { const userMessages = messageQueue.get(userId) || []; const filtered = userMessages.filter(m => !messageIds.includes(m.messageId)); messageQueue.set(userId, filtered); } /** * 推送绑定即将过期提醒 */ export function pushBindingExpiringReminder(params: { userId: string; bindingId: string; visitorNickname?: string; visitorPhone?: string; daysRemaining: number; expireTime: string; }): void { pushMessage({ type: 'binding_expiring', userId: params.userId, data: { bindingId: params.bindingId, visitorNickname: params.visitorNickname, visitorPhone: params.visitorPhone, daysRemaining: params.daysRemaining, expireTime: params.expireTime, message: `用户 ${params.visitorNickname || params.visitorPhone || '未知'} 的绑定将在 ${params.daysRemaining} 天后过期`, }, }); } /** * 推送绑定已过期通知 */ export function pushBindingExpiredNotice(params: { userId: string; bindingId: string; visitorNickname?: string; visitorPhone?: string; }): void { pushMessage({ type: 'binding_expired', userId: params.userId, data: { bindingId: params.bindingId, visitorNickname: params.visitorNickname, visitorPhone: params.visitorPhone, message: `用户 ${params.visitorNickname || params.visitorPhone || '未知'} 的绑定已过期`, }, }); } /** * 推送绑定转化通知(用户付款) */ export function pushBindingConvertedNotice(params: { userId: string; bindingId: string; orderId: string; orderAmount: number; commission: number; visitorNickname?: string; }): void { pushMessage({ type: 'binding_converted', userId: params.userId, data: { bindingId: params.bindingId, orderId: params.orderId, orderAmount: params.orderAmount, commission: params.commission, visitorNickname: params.visitorNickname, message: `恭喜!用户 ${params.visitorNickname || '未知'} 已付款 ¥${params.orderAmount},您获得佣金 ¥${params.commission.toFixed(2)}`, }, }); } /** * 推送提现状态更新 */ export function pushWithdrawalUpdate(params: { userId: string; withdrawalId: string; amount: number; status: 'approved' | 'completed' | 'rejected'; paymentNo?: string; rejectReason?: string; }): void { const type: WebSocketMessageType = params.status === 'approved' ? 'withdrawal_approved' : params.status === 'completed' ? 'withdrawal_completed' : 'withdrawal_rejected'; const messages: Record = { approved: `您的提现申请 ¥${params.amount.toFixed(2)} 已通过审核,正在打款中...`, completed: `您的提现 ¥${params.amount.toFixed(2)} 已成功到账,流水号: ${params.paymentNo}`, rejected: `您的提现申请 ¥${params.amount.toFixed(2)} 已被拒绝,原因: ${params.rejectReason || '未说明'}`, }; pushMessage({ type, userId: params.userId, data: { withdrawalId: params.withdrawalId, amount: params.amount, status: params.status, paymentNo: params.paymentNo, rejectReason: params.rejectReason, message: messages[params.status], }, }); } /** * 推送收益增加通知 */ export function pushEarningsAdded(params: { userId: string; orderId: string; orderAmount: number; commission: number; visitorNickname?: string; }): void { pushMessage({ type: 'earnings_added', userId: params.userId, data: { orderId: params.orderId, orderAmount: params.orderAmount, commission: params.commission, visitorNickname: params.visitorNickname, message: `收益 +¥${params.commission.toFixed(2)}`, }, }); } /** * 推送系统通知 */ export function pushSystemNotice(params: { userId: string; title: string; content: string; link?: string; }): void { pushMessage({ type: 'system_notice', userId: params.userId, data: { title: params.title, content: params.content, link: params.link, }, }); } /** * 客户端WebSocket Hook(用于React组件) * 使用轮询模式获取实时消息 */ export function createWebSocketClient(userId: string, onMessage: (message: WebSocketMessage) => void) { let lastTimestamp = new Date().toISOString(); let isRunning = false; let intervalId: NodeJS.Timeout | null = null; const fetchMessages = async () => { if (!isRunning) return; try { const response = await fetch(`/api/distribution/messages?userId=${userId}&since=${encodeURIComponent(lastTimestamp)}`); if (!response.ok) return; const data = await response.json(); if (data.success && data.messages?.length > 0) { for (const message of data.messages) { onMessage(message); if (message.timestamp > lastTimestamp) { lastTimestamp = message.timestamp; } } } } catch (error) { console.error('[WebSocketClient] 获取消息失败:', error); } }; return { connect: () => { isRunning = true; // 每3秒轮询一次 intervalId = setInterval(fetchMessages, 3000); // 立即获取一次 fetchMessages(); console.log('[WebSocketClient] 已连接,用户:', userId); }, disconnect: () => { isRunning = false; if (intervalId) { clearInterval(intervalId); intervalId = null; } console.log('[WebSocketClient] 已断开连接'); }, isConnected: () => isRunning, }; }