// src/services/mockSocketService.js /** * Mock Socket 服务 - 用于开发环境模拟实时推送 * 模拟金融资讯、事件动向、分析报告等实时消息推送 */ import { logger } from '../utils/logger'; import { NOTIFICATION_TYPES, PRIORITY_LEVELS } from '../constants/notificationTypes'; // 模拟金融资讯数据 const mockFinancialNews = [ // ========== 公告通知 ========== { type: NOTIFICATION_TYPES.ANNOUNCEMENT, priority: PRIORITY_LEVELS.IMPORTANT, title: '贵州茅台发布2024年度财报公告', content: '2024年度营收同比增长15.2%,净利润创历史新高,董事会建议每10股派息180元', publishTime: new Date('2024-03-28T15:30:00').getTime(), pushTime: Date.now(), isAIGenerated: false, clickable: true, link: '/event-detail/ann001', extra: { announcementType: '财报', companyCode: '600519', companyName: '贵州茅台', }, autoClose: 10000, }, { type: NOTIFICATION_TYPES.ANNOUNCEMENT, priority: PRIORITY_LEVELS.URGENT, title: '宁德时代发布重大资产重组公告', content: '公司拟收购某新能源材料公司100%股权,交易金额约120亿元,预计增厚业绩20%', publishTime: new Date('2024-03-28T09:00:00').getTime(), pushTime: Date.now(), isAIGenerated: false, clickable: true, link: '/event-detail/ann002', extra: { announcementType: '重组', companyCode: '300750', companyName: '宁德时代', }, autoClose: 12000, }, { type: NOTIFICATION_TYPES.ANNOUNCEMENT, priority: PRIORITY_LEVELS.NORMAL, title: '中国平安发布分红派息公告', content: '2023年度利润分配方案:每10股派发现金红利23.0元(含税),分红率达30.5%', publishTime: new Date('2024-03-27T16:00:00').getTime(), pushTime: Date.now(), isAIGenerated: false, clickable: true, link: '/event-detail/ann003', extra: { announcementType: '分红', companyCode: '601318', companyName: '中国平安', }, autoClose: 10000, }, // ========== 股票动向 ========== { type: NOTIFICATION_TYPES.STOCK_ALERT, priority: PRIORITY_LEVELS.URGENT, title: '您关注的股票触发预警', content: '宁德时代(300750) 当前价格 ¥245.50,盘中涨幅达 +5.2%,已触达您设置的目标价位', publishTime: Date.now(), pushTime: Date.now(), isAIGenerated: false, clickable: true, link: '/stock-overview?code=300750', extra: { stockCode: '300750', stockName: '宁德时代', priceChange: '+5.2%', currentPrice: '245.50', triggerType: '目标价', }, autoClose: 10000, }, { type: NOTIFICATION_TYPES.STOCK_ALERT, priority: PRIORITY_LEVELS.IMPORTANT, title: '您关注的股票异常波动', content: '比亚迪(002594) 5分钟内跌幅达 -3.8%,当前价格 ¥198.20,建议关注', publishTime: Date.now(), pushTime: Date.now(), isAIGenerated: false, clickable: true, link: '/stock-overview?code=002594', extra: { stockCode: '002594', stockName: '比亚迪', priceChange: '-3.8%', currentPrice: '198.20', triggerType: '异常波动', }, autoClose: 10000, }, { type: NOTIFICATION_TYPES.STOCK_ALERT, priority: PRIORITY_LEVELS.NORMAL, title: '持仓股票表现', content: '隆基绿能(601012) 今日表现优异,涨幅 +4.5%,您当前持仓浮盈 +¥8,200', publishTime: Date.now(), pushTime: Date.now(), isAIGenerated: false, clickable: true, link: '/trading-simulation', extra: { stockCode: '601012', stockName: '隆基绿能', priceChange: '+4.5%', profit: '+8200', }, autoClose: 8000, }, // ========== 事件动向 ========== { type: NOTIFICATION_TYPES.EVENT_ALERT, priority: PRIORITY_LEVELS.IMPORTANT, title: '央行宣布降准0.5个百分点', content: '中国人民银行宣布下调金融机构存款准备金率0.5个百分点,释放长期资金约1万亿元,利好股市', publishTime: new Date('2024-03-28T09:00:00').getTime(), pushTime: Date.now(), isAIGenerated: false, clickable: true, link: '/event-detail/evt001', extra: { eventId: 'evt001', relatedStocks: 12, impactLevel: '重大利好', sectors: ['银行', '地产', '基建'], }, autoClose: 12000, }, { type: NOTIFICATION_TYPES.EVENT_ALERT, priority: PRIORITY_LEVELS.IMPORTANT, title: '新能源汽车补贴政策延期', content: '财政部宣布新能源汽车购置补贴政策延长至2024年底,涉及比亚迪、理想汽车等5家龙头企业', publishTime: new Date('2024-03-28T10:30:00').getTime(), pushTime: Date.now(), isAIGenerated: false, clickable: true, link: '/event-detail/evt002', extra: { eventId: 'evt002', relatedStocks: 5, impactLevel: '重大利好', sectors: ['新能源汽车'], }, autoClose: 12000, }, { type: NOTIFICATION_TYPES.EVENT_ALERT, priority: PRIORITY_LEVELS.NORMAL, title: '芯片产业扶持政策出台', content: '工信部发布《半导体产业发展指导意见》,未来三年投入500亿专项资金支持芯片研发', publishTime: new Date('2024-03-27T14:00:00').getTime(), pushTime: Date.now(), isAIGenerated: false, clickable: true, link: '/event-detail/evt003', extra: { eventId: 'evt003', relatedStocks: 8, impactLevel: '中长期利好', sectors: ['半导体', '芯片设计'], }, autoClose: 10000, }, // ========== 预测通知 ========== { type: NOTIFICATION_TYPES.EVENT_ALERT, priority: PRIORITY_LEVELS.NORMAL, title: '【预测】央行可能宣布降准政策', content: '基于最新宏观数据分析,预计央行将在本周宣布降准0.5个百分点,释放长期资金', publishTime: Date.now(), pushTime: Date.now(), isAIGenerated: true, clickable: false, // ❌ 不可点击 link: null, extra: { isPrediction: true, statusHint: '详细报告生成中...', relatedPredictionId: 'pred_001', }, autoClose: 15000, }, { type: NOTIFICATION_TYPES.EVENT_ALERT, priority: PRIORITY_LEVELS.NORMAL, title: '【预测】新能源补贴政策或将延期', content: '根据政策趋势分析,财政部可能宣布新能源汽车购置补贴政策延长至2025年底', publishTime: Date.now(), pushTime: Date.now(), isAIGenerated: true, clickable: false, // ❌ 不可点击 link: null, extra: { isPrediction: true, statusHint: '详细报告生成中...', relatedPredictionId: 'pred_002', }, autoClose: 15000, }, // ========== 分析报告 ========== { type: NOTIFICATION_TYPES.ANALYSIS_REPORT, priority: PRIORITY_LEVELS.IMPORTANT, title: '医药行业深度报告:创新药迎来政策拐点', content: 'CXO板块持续受益于全球创新药研发外包需求,建议关注药明康德、凯莱英等龙头企业', publishTime: new Date('2024-03-28T08:00:00').getTime(), pushTime: Date.now(), author: { name: '李明', organization: '中信证券', }, isAIGenerated: false, clickable: true, link: '/forecast-report?id=rpt001', extra: { reportType: '行业研报', industry: '医药', rating: '强烈推荐', }, autoClose: 12000, }, { type: NOTIFICATION_TYPES.ANALYSIS_REPORT, priority: PRIORITY_LEVELS.IMPORTANT, title: 'AI产业链投资机会分析', content: '随着大模型应用加速落地,算力、数据、应用三大方向均存在投资机会,重点关注海光信息、寒武纪', publishTime: new Date('2024-03-28T07:30:00').getTime(), pushTime: Date.now(), author: { name: '王芳', organization: '招商证券', }, isAIGenerated: true, clickable: true, link: '/forecast-report?id=rpt002', extra: { reportType: '策略报告', industry: '人工智能', rating: '推荐', }, autoClose: 12000, }, { type: NOTIFICATION_TYPES.ANALYSIS_REPORT, priority: PRIORITY_LEVELS.NORMAL, title: '比亚迪:新能源汽车龙头业绩持续超预期', content: '2024年销量目标400万辆,海外市场拓展顺利,维持"买入"评级,目标价280元', publishTime: new Date('2024-03-27T09:00:00').getTime(), pushTime: Date.now(), author: { name: '张伟', organization: '国泰君安', }, isAIGenerated: false, clickable: true, link: '/forecast-report?id=rpt003', extra: { reportType: '公司研报', industry: '新能源汽车', rating: '买入', targetPrice: '280', }, autoClose: 10000, }, { type: NOTIFICATION_TYPES.ANALYSIS_REPORT, priority: PRIORITY_LEVELS.NORMAL, title: '2024年A股市场展望:结构性行情延续', content: 'AI应用、高端制造、自主可控三大主线贯穿全年,建议关注科技成长板块配置机会', publishTime: new Date('2024-03-26T16:00:00').getTime(), pushTime: Date.now(), author: { name: 'AI分析师', organization: '价值前沿', }, isAIGenerated: true, clickable: true, link: '/forecast-report?id=rpt004', extra: { reportType: '策略报告', industry: '市场策略', rating: '谨慎乐观', }, autoClose: 10000, }, ]; class MockSocketService { constructor() { this.connected = false; this.listeners = new Map(); this.intervals = []; this.messageQueue = []; this.reconnectAttempts = 0; this.customReconnectTimer = null; this.failConnection = false; // 是否模拟连接失败 } /** * 计算指数退避延迟(Mock 模式使用更短的时间便于测试) * 第1次: 10秒, 第2次: 20秒, 第3次: 40秒, 第4次及以后: 40秒 */ getReconnectionDelay(attempt) { const delays = [10000, 20000, 40000]; // 10s, 20s, 40s (缩短10倍便于测试) const index = Math.min(attempt - 1, delays.length - 1); return delays[index]; } /** * 连接到 mock socket */ connect() { if (this.connected) { logger.warn('mockSocketService', 'Already connected'); return; } logger.info('mockSocketService', 'Connecting to mock socket service...'); // 模拟连接延迟 setTimeout(() => { // 检查是否应该模拟连接失败 if (this.failConnection) { logger.warn('mockSocketService', 'Simulated connection failure'); // 触发连接错误事件 this.emit('connect_error', { message: 'Mock connection error for testing', timestamp: Date.now(), }); // 安排下次重连(会继续失败,直到 failConnection 被清除) this.scheduleReconnection(); return; } // 正常连接成功 this.connected = true; this.reconnectAttempts = 0; // 清除自定义重连定时器 if (this.customReconnectTimer) { clearTimeout(this.customReconnectTimer); this.customReconnectTimer = null; } logger.info('mockSocketService', 'Mock socket connected successfully'); // 触发连接成功事件 this.emit('connect', { timestamp: Date.now() }); // 在连接后3秒发送欢迎消息 setTimeout(() => { if (this.connected) { this.emit('new_event', { type: 'system_notification', severity: 'info', title: '连接成功', message: '实时消息推送服务已启动 (Mock 模式)', timestamp: Date.now(), autoClose: 5000, }); } }, 3000); }, 1000); } /** * 断开连接 * @param {boolean} triggerReconnect - 是否触发自动重连(模拟意外断开) */ disconnect(triggerReconnect = false) { if (!this.connected) { return; } logger.info('mockSocketService', 'Disconnecting from mock socket service...'); // 清除所有定时器 this.intervals.forEach(interval => clearInterval(interval)); this.intervals = []; const wasConnected = this.connected; this.connected = false; this.emit('disconnect', { timestamp: Date.now(), reason: triggerReconnect ? 'transport close' : 'io client disconnect' }); // 如果需要触发重连(模拟意外断开) if (triggerReconnect && wasConnected) { this.scheduleReconnection(); } else { // 清除重连定时器 if (this.customReconnectTimer) { clearTimeout(this.customReconnectTimer); this.customReconnectTimer = null; } this.reconnectAttempts = 0; } } /** * 使用指数退避策略安排重连 */ scheduleReconnection() { // 清除之前的定时器 if (this.customReconnectTimer) { clearTimeout(this.customReconnectTimer); } this.reconnectAttempts++; const delay = this.getReconnectionDelay(this.reconnectAttempts); logger.info('mockSocketService', `Scheduling reconnection in ${delay / 1000}s (attempt ${this.reconnectAttempts})`); // 触发 connect_error 事件通知UI this.emit('connect_error', { message: 'Mock connection error for testing', timestamp: Date.now(), }); this.customReconnectTimer = setTimeout(() => { if (!this.connected) { logger.info('mockSocketService', 'Attempting reconnection...', { attempt: this.reconnectAttempts, }); this.connect(); } }, delay); } /** * 手动重连 * @returns {boolean} 是否触发重连 */ reconnect() { if (this.connected) { logger.info('mockSocketService', 'Already connected, no need to reconnect'); return false; } logger.info('mockSocketService', 'Manually triggering reconnection...'); // 清除自动重连定时器 if (this.customReconnectTimer) { clearTimeout(this.customReconnectTimer); this.customReconnectTimer = null; } // 重置重连计数 this.reconnectAttempts = 0; // 立即触发重连 this.connect(); return true; } /** * 模拟意外断线(测试用) * @param {number} duration - 断线持续时间(毫秒),0表示需要手动重连 */ simulateDisconnection(duration = 0) { logger.info('mockSocketService', `Simulating disconnection${duration > 0 ? ` for ${duration}ms` : ' (manual reconnect required)'}...`); if (duration > 0) { // 短暂断线,自动重连 this.disconnect(true); } else { // 需要手动重连 this.disconnect(false); } } /** * 模拟持续连接失败(测试用) * 连接会一直失败,直到调用 allowReconnection() */ simulateConnectionFailure() { logger.info('mockSocketService', '🚫 Simulating persistent connection failure...'); logger.info('mockSocketService', 'Connection will keep failing until allowReconnection() is called'); // 设置失败标志 this.failConnection = true; // 如果当前已连接,先断开并触发重连(会失败) if (this.connected) { this.disconnect(true); } else { // 如果未连接,直接触发一次连接尝试(会失败) this.connect(); } } /** * 允许重连成功(测试用) * 清除连接失败标志,下次重连将会成功 */ allowReconnection() { logger.info('mockSocketService', '✅ Allowing reconnection to succeed...'); logger.info('mockSocketService', 'Next reconnection attempt will succeed'); // 清除失败标志 this.failConnection = false; // 不立即重连,等待自动重连或手动重连 } /** * 监听事件 * @param {string} event - 事件名称 * @param {Function} callback - 回调函数 */ on(event, callback) { if (!this.listeners.has(event)) { this.listeners.set(event, []); } this.listeners.get(event).push(callback); logger.info('mockSocketService', `Event listener added: ${event}`); } /** * 移除事件监听 * @param {string} event - 事件名称 * @param {Function} callback - 回调函数 */ off(event, callback) { if (!this.listeners.has(event)) { return; } const callbacks = this.listeners.get(event); const index = callbacks.indexOf(callback); if (index !== -1) { callbacks.splice(index, 1); logger.info('mockSocketService', `Event listener removed: ${event}`); } // 如果没有监听器了,删除该事件 if (callbacks.length === 0) { this.listeners.delete(event); } } /** * 触发事件 * @param {string} event - 事件名称 * @param {*} data - 事件数据 */ emit(event, data) { if (!this.listeners.has(event)) { return; } const callbacks = this.listeners.get(event); callbacks.forEach(callback => { try { callback(data); } catch (error) { logger.error('mockSocketService', 'emit', error, { event, data }); } }); } /** * 启动模拟消息推送 * @param {number} interval - 推送间隔(毫秒) * @param {number} burstCount - 每次推送的消息数量(1-3条) */ startMockPush(interval = 15000, burstCount = 1) { if (!this.connected) { logger.warn('mockSocketService', 'Cannot start mock push: not connected'); return; } logger.info('mockSocketService', `Starting mock push: interval=${interval}ms, burst=${burstCount}`); const pushInterval = setInterval(() => { // 随机选择 1-burstCount 条消息 const count = Math.floor(Math.random() * burstCount) + 1; for (let i = 0; i < count; i++) { // 从模拟数据中随机选择一条 const randomIndex = Math.floor(Math.random() * mockFinancialNews.length); const alert = { ...mockFinancialNews[randomIndex], timestamp: Date.now(), id: `mock_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, }; // 延迟发送(模拟层叠效果) setTimeout(() => { this.emit('new_event', alert); logger.info('mockSocketService', 'Mock notification sent', alert); }, i * 500); // 每条消息间隔500ms } }, interval); this.intervals.push(pushInterval); } /** * 停止模拟推送 */ stopMockPush() { this.intervals.forEach(interval => clearInterval(interval)); this.intervals = []; logger.info('mockSocketService', 'Mock push stopped'); } /** * 手动触发一条测试消息 * @param {object} customData - 自定义消息数据(可选) */ sendTestNotification(customData = null) { const notification = customData || { type: 'trade_alert', severity: 'info', title: '测试消息', message: '这是一条手动触发的测试消息', timestamp: Date.now(), autoClose: 5000, id: `test_${Date.now()}`, }; this.emit('new_event', notification); logger.info('mockSocketService', 'Test notification sent', notification); } /** * 获取连接状态 */ isConnected() { return this.connected; } /** * 获取当前重连尝试次数 */ getReconnectAttempts() { return this.reconnectAttempts; } /** * 获取最大重连次数(Mock 模式无限重试) */ getMaxReconnectAttempts() { return Infinity; } } // 导出单例 export const mockSocketService = new MockSocketService(); // 开发模式下添加全局测试函数 if (process.env.NODE_ENV === 'development') { window.__mockSocket = { // 模拟意外断线(自动重连成功) simulateDisconnection: () => { logger.info('mockSocketService', '🔌 Simulating disconnection (will auto-reconnect)...'); mockSocketService.simulateDisconnection(1); // 触发自动重连 }, // 模拟持续连接失败 simulateConnectionFailure: () => { logger.info('mockSocketService', '🚫 Simulating connection failure (will keep retrying)...'); mockSocketService.simulateConnectionFailure(); }, // 允许重连成功 allowReconnection: () => { logger.info('mockSocketService', '✅ Allowing next reconnection to succeed...'); mockSocketService.allowReconnection(); }, // 获取连接状态 isConnected: () => { const connected = mockSocketService.isConnected(); logger.info('mockSocketService', `Connection status: ${connected ? '✅ Connected' : '❌ Disconnected'}`); return connected; }, // 手动重连 reconnect: () => { logger.info('mockSocketService', '🔄 Manually triggering reconnection...'); return mockSocketService.reconnect(); }, // 获取重连尝试次数 getAttempts: () => { const attempts = mockSocketService.getReconnectAttempts(); logger.info('mockSocketService', `Current reconnection attempts: ${attempts}`); return attempts; }, }; logger.info('mockSocketService', '💡 Mock Socket test functions available:'); logger.info('mockSocketService', ' __mockSocket.simulateDisconnection() - 模拟断线(自动重连成功)'); logger.info('mockSocketService', ' __mockSocket.simulateConnectionFailure() - 模拟连接失败(持续失败)'); logger.info('mockSocketService', ' __mockSocket.allowReconnection() - 允许重连成功'); logger.info('mockSocketService', ' __mockSocket.isConnected() - 查看连接状态'); logger.info('mockSocketService', ' __mockSocket.reconnect() - 手动重连'); logger.info('mockSocketService', ' __mockSocket.getAttempts() - 查看重连次数'); } export default mockSocketService;