const net = require('net'); const { JhmDecoder } = require('./decoder'); function normalizeIp(address) { if (!address) { return ''; } if (address.startsWith('::ffff:')) { return address.slice(7); } return address; } function formatReceivedTimestamp(date = new Date()) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hour = String(date.getHours()).padStart(2, '0'); const minute = String(date.getMinutes()).padStart(2, '0'); const second = String(date.getSeconds()).padStart(2, '0'); return `${year}-${month}-${day} ${hour}:${minute}:${second}`; } class TcpService { constructor(options) { this.tcpConfig = options.tcpConfig; this.devices = options.devices || []; this.alModelPath = options.alModelPath; this.publishBloodPressureTime = options.publishBloodPressureTime !== false; this.logger = options.logger || console; this.onMetric = typeof options.onMetric === 'function' ? options.onMetric : () => {}; this.server = null; this.sessions = new Map(); this.deviceMap = this.buildDeviceMap(this.devices); } buildDeviceMap(devices) { const map = new Map(); for (const device of devices) { if (!device || !device.ip || !device.deviceId) { continue; } map.set(normalizeIp(device.ip), { ...device, name: device.name || device['备注'] || device.deviceId, }); } return map; } getReceivedMetricTime() { return formatReceivedTimestamp(); } async start() { this.server = net.createServer((socket) => { this.handleConnection(socket); }); this.server.maxConnections = this.tcpConfig.maxConnections || 100; this.server.on('error', (error) => { this.logger.error(`[TCP] 服务错误: ${error.message}`); }); await new Promise((resolve, reject) => { this.server.once('listening', resolve); this.server.once('error', reject); this.server.listen({ host: this.tcpConfig.host, port: this.tcpConfig.port, backlog: this.tcpConfig.backlog || 128, }); }); this.logger.info(`[TCP] 已监听 ${this.tcpConfig.host}:${this.tcpConfig.port} devices=${this.deviceMap.size} maxConnections=${this.server.maxConnections}`); } handleConnection(socket) { const clientIp = normalizeIp(socket.remoteAddress); const device = this.deviceMap.get(clientIp); if (!device) { this.logger.warn(`[TCP] 未配置设备接入,ip=${clientIp} port=${socket.remotePort},连接将被关闭`); socket.destroy(); return; } const decoder = new JhmDecoder({ alModelPath: this.alModelPath, maxBufferBytes: this.tcpConfig.maxBufferBytes || 8192, }); this.sessions.set(socket, { clientIp, device, decoder, }); if (this.tcpConfig.keepAlive) { socket.setKeepAlive(true, this.tcpConfig.keepAliveDelayMs || 10000); } socket.setNoDelay(Boolean(this.tcpConfig.noDelay)); socket.setTimeout(this.tcpConfig.socketTimeoutMs || 120000); this.logger.info(`[TCP] 设备已连接 deviceId=${device.deviceId} ip=${clientIp} port=${socket.remotePort} activeSessions=${this.sessions.size}`); socket.on('data', (chunk) => { this.handleData(socket, chunk); }); socket.on('timeout', () => { this.logger.warn(`[TCP] 连接超时 deviceId=${device.deviceId} ip=${clientIp}`); socket.destroy(); }); socket.on('error', (error) => { this.logger.error(`[TCP] 连接异常 deviceId=${device.deviceId} ip=${clientIp}: ${error.message}`); }); socket.on('close', () => { this.sessions.delete(socket); this.logger.info(`[TCP] 设备已断开 deviceId=${device.deviceId} ip=${clientIp} activeSessions=${this.sessions.size}`); }); } handleData(socket, chunk) { const session = this.sessions.get(socket); if (!session) { return; } this.logger.info(`[TCP] 收到原始数据 deviceId=${session.device.deviceId} bytes=${chunk.length}`); const results = session.decoder.push(chunk); if (results.length === 0) { this.logger.warn(`[TCP] 当前数据片段未形成完整报文 deviceId=${session.device.deviceId} bytes=${chunk.length}`); } for (const result of results) { if (!result.publish) { if (!result.ok) { this.logger.warn(`[TCP] 丢弃无效报文 deviceId=${session.device.deviceId} reason=${result.reason}`); } else { this.logger.warn(`[TCP] 跳过未发布报文 deviceId=${session.device.deviceId} reason=${result.reason || 'publish-disabled'} raw=${result.rawHex || ''}`); } continue; } if (result.protocol === 'blood-pressure' && this.publishBloodPressureTime) { result.metric = { ...result.metric, M: this.getReceivedMetricTime(), }; } this.logger.info(`[TCP] 收到指标 deviceId=${session.device.deviceId} metric=${JSON.stringify(result.metric)} raw=${result.rawHex}`); this.onMetric(session.device, result.metric, result); } } async stop() { this.logger.info(`[TCP] 开始停止 TCP 服务 activeSessions=${this.sessions.size}`); for (const socket of this.sessions.keys()) { socket.destroy(); } this.sessions.clear(); if (!this.server) { return; } await new Promise((resolve) => { this.server.close(resolve); }); this.server = null; this.logger.info('[TCP] TCP 服务已停止'); } } module.exports = { formatReceivedTimestamp, TcpService, normalizeIp, };