Files
soul/lib/modules/distribution/websocket.ts
卡若 b60edb3d47 feat: 完整重构小程序匹配功能 + 修复UI对齐 + 文章数据API
主要更新:
1. 按H5网页端完全重构匹配功能(match页面)
   - 4种匹配类型: 创业合伙/资源对接/导师顾问/团队招募
   - 资源对接等类型弹出手机号/微信号输入框
   - 去掉重新匹配按钮,改为返回按钮

2. 修复所有卡片对齐和宽度问题
   - 目录页附录卡片居中
   - 首页阅读进度卡片满宽度
   - 我的页面菜单卡片对齐
   - 推广中心分享卡片统一宽度

3. 修复目录页图标和文字对齐
   - section-icon固定40rpx宽高
   - section-title与图标垂直居中

4. 更新真实完整文章标题(62篇)
   - 从book目录读取真实markdown文件名
   - 替换之前的简化标题

5. 新增文章数据API
   - /api/db/chapters - 获取完整书籍结构
   - 支持按ID获取单篇文章内容
2026-01-21 15:49:12 +08:00

315 lines
8.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 分销模块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<string, unknown>;
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<string, WebSocketMessage[]> = new Map();
/**
* 生成消息ID
*/
function generateMessageId(): string {
return `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
/**
* 添加消息到队列
*/
export function pushMessage(message: Omit<WebSocketMessage, 'messageId' | 'timestamp'>): 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<string, string> = {
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,
};
}