autosnippet
Version:
Extract code patterns into a knowledge base for AI coding assistants
135 lines (134 loc) • 4.52 kB
JavaScript
/**
* WebSocket/Socket.io 实时通知服务
* 提供候选人、食谱、规则的实时更新通知
*/
import { Server as SocketIOServer } from 'socket.io';
import Logger from '../logging/Logger.js';
export class RealtimeService {
io;
constructor(httpServer) {
this.io = new SocketIOServer(httpServer, {
cors: {
origin: '*',
methods: ['GET', 'POST'],
},
transports: ['websocket', 'polling'],
pingInterval: 25000, // 25s 心跳间隔(默认值,显式声明)
pingTimeout: 20000, // 20s 超时(默认值)
});
this.setupEventHandlers();
}
/** 设置事件处理器 */
setupEventHandlers() {
this.io.on('connection', (socket) => {
Logger.debug(`[Socket.io] Client connected: ${socket.id}`);
// 加入通知房间
socket.on('join-notifications', () => {
socket.join('notifications');
socket.emit('notification-joined', {
message: '已连接到实时通知',
timestamp: Date.now(),
});
});
// 离开通知房间
socket.on('leave-notifications', () => {
socket.leave('notifications');
});
// 处理断开连接
socket.on('disconnect', () => {
Logger.debug(`[Socket.io] Client disconnected: ${socket.id}`);
});
// 健康检查
socket.on('ping', () => {
socket.emit('pong', { timestamp: Date.now() });
});
});
}
/** 广播候选人创建事件 */
broadcastCandidateCreated(candidate) {
this.io.to('notifications').emit('candidate-created', {
type: 'candidate_created',
candidate,
timestamp: Date.now(),
});
}
/** 广播候选人状态变化事件 */
broadcastCandidateStatusChanged(candidateId, newStatus, oldStatus) {
this.io.to('notifications').emit('candidate-status-changed', {
type: 'candidate_status_changed',
candidateId,
newStatus,
oldStatus,
timestamp: Date.now(),
});
}
/** 广播 Token 用量变化事件(Sidebar 指标刷新用) */
broadcastTokenUsageUpdated() {
this.io.to('notifications').emit('token-usage-updated', {
type: 'token_usage_updated',
timestamp: Date.now(),
});
}
/** 广播食谱创建事件 */
broadcastRecipeCreated(recipe) {
this.io.to('notifications').emit('recipe-created', {
type: 'recipe_created',
recipe,
timestamp: Date.now(),
});
}
/** 广播食谱发布事件 */
broadcastRecipePublished(recipeId, recipe) {
this.io.to('notifications').emit('recipe-published', {
type: 'recipe_published',
recipeId,
recipe,
timestamp: Date.now(),
});
}
/** 广播规则创建事件 */
broadcastRuleCreated(rule) {
this.io.to('notifications').emit('rule-created', {
type: 'rule_created',
rule,
timestamp: Date.now(),
});
}
/** 广播规则状态变化事件 */
broadcastRuleStatusChanged(ruleId, enabled) {
this.io.to('notifications').emit('rule-status-changed', {
type: 'rule_status_changed',
ruleId,
enabled,
timestamp: Date.now(),
});
}
/** 广播通用事件 */
broadcastEvent(eventName, data) {
// 直接透传 data(不包装 type/timestamp),保持与前端 hook 期望的数据结构一致
this.io.to('notifications').emit(eventName, data);
}
/** 获取 Socket.io 实例 */
getIO() {
return this.io;
}
/** 获取连接的客户端数量 */
getConnectedClients() {
return this.io.engine.clientsCount;
}
}
// 单例实例
let realtimeService = null;
export function initRealtimeService(httpServer) {
if (!realtimeService) {
realtimeService = new RealtimeService(httpServer);
Logger.info('✅ RealtimeService initialized');
}
return realtimeService;
}
export function getRealtimeService() {
if (!realtimeService) {
throw new Error('RealtimeService not initialized. Call initRealtimeService() first.');
}
return realtimeService;
}