Files
sms_forwarding/src/concat.js
T
2026-06-27 09:27:16 +08:00

173 lines
4.4 KiB
JavaScript

import EventEmitter from 'events';
import logger from './logger.js';
class ConcatManager extends EventEmitter {
constructor() {
super();
this.buffer = [];
this.maxSlots = 5;
this.maxParts = 10;
this.timeoutMs = 30000; // 30秒超时
}
/**
* 查找或创建长短信槽位
*/
findOrCreateSlot(refNumber, sender, totalParts, simIdentity = null) {
const simId = simIdentity?.simId || '';
// 先查找是否已存在
let slot = this.buffer.find(s =>
s.inUse && s.refNumber === refNumber && s.sender === sender && s.simId === simId
);
if (slot) {
return slot;
}
// 查找空闲槽位
slot = this.buffer.find(s => !s.inUse);
if (slot) {
this.initSlot(slot, refNumber, sender, totalParts, simIdentity);
return slot;
}
// 没有空闲槽位,查找最老的槽位覆盖
if (this.buffer.length < this.maxSlots) {
slot = {
inUse: false,
parts: []
};
this.buffer.push(slot);
} else {
slot = this.buffer.reduce((oldest, current) =>
current.firstPartTime < oldest.firstPartTime ? current : oldest
);
logger.warn('长短信缓存已满,覆盖最老的槽位');
}
this.initSlot(slot, refNumber, sender, totalParts, simIdentity);
return slot;
}
/**
* 初始化槽位
*/
initSlot(slot, refNumber, sender, totalParts, simIdentity = null) {
slot.inUse = true;
slot.refNumber = refNumber;
slot.sender = sender;
slot.simId = simIdentity?.simId || '';
slot.simIdentity = simIdentity;
slot.totalParts = totalParts;
slot.receivedParts = 0;
slot.firstPartTime = Date.now();
slot.timestamp = null;
slot.parts = new Array(totalParts).fill(null);
}
/**
* 添加短信分段
*/
addPart(refNumber, sender, partNumber, totalParts, text, timestamp, options = {}) {
logger.info(`收到长短信分段 ${partNumber}/${totalParts}, 参考号: ${refNumber}`);
const slot = this.findOrCreateSlot(refNumber, sender, totalParts, options.simIdentity);
const partIndex = partNumber - 1; // partNumber从1开始,数组从0开始
if (partIndex >= 0 && partIndex < this.maxParts) {
if (!slot.parts[partIndex]) {
slot.parts[partIndex] = text;
slot.receivedParts++;
// 保存第一个收到的分段的时间戳
if (slot.receivedParts === 1) {
slot.timestamp = timestamp;
}
logger.info(`已缓存分段 ${partNumber},当前已收到 ${slot.receivedParts}/${totalParts}`);
// 检查是否已收齐
if (slot.receivedParts >= totalParts) {
logger.info('✅ 长短信已收齐,开始合并转发');
this.assembleAndEmit(slot);
}
} else {
logger.warn(`⚠️ 分段 ${partNumber} 已存在,跳过`);
}
}
}
/**
* 合并并发射完整短信
*/
assembleAndEmit(slot) {
let fullText = '';
for (let i = 0; i < slot.totalParts; i++) {
if (slot.parts[i]) {
fullText += slot.parts[i];
} else {
fullText += `[缺失分段${i + 1}]`;
}
}
this.emit('complete', {
sender: slot.sender,
text: fullText,
timestamp: slot.timestamp,
simIdentity: slot.simIdentity
});
// 清空槽位
this.clearSlot(slot);
}
/**
* 清空槽位
*/
clearSlot(slot) {
slot.inUse = false;
slot.parts = [];
slot.receivedParts = 0;
slot.simId = '';
slot.simIdentity = null;
}
/**
* 检查超时
*/
checkTimeout() {
const now = Date.now();
this.buffer.forEach(slot => {
if (slot.inUse && (now - slot.firstPartTime) >= this.timeoutMs) {
logger.warn(`⏰ 长短信超时,强制转发不完整消息`);
logger.warn(` 参考号: ${slot.refNumber}, 已收到: ${slot.receivedParts}/${slot.totalParts}`);
this.assembleAndEmit(slot);
}
});
}
/**
* 启动超时检查定时器
*/
startTimeoutChecker() {
this.timeoutChecker = setInterval(() => {
this.checkTimeout();
}, 5000); // 每5秒检查一次
logger.info('长短信超时检查器已启动');
}
/**
* 停止超时检查定时器
*/
stopTimeoutChecker() {
if (this.timeoutChecker) {
clearInterval(this.timeoutChecker);
this.timeoutChecker = null;
logger.info('长短信超时检查器已停止');
}
}
}
export default ConcatManager;