feat: 初始化短信网关核心
This commit is contained in:
+165
@@ -0,0 +1,165 @@
|
||||
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) {
|
||||
// 先查找是否已存在
|
||||
let slot = this.buffer.find(s =>
|
||||
s.inUse && s.refNumber === refNumber && s.sender === sender
|
||||
);
|
||||
|
||||
if (slot) {
|
||||
return slot;
|
||||
}
|
||||
|
||||
// 查找空闲槽位
|
||||
slot = this.buffer.find(s => !s.inUse);
|
||||
if (slot) {
|
||||
this.initSlot(slot, refNumber, sender, totalParts);
|
||||
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);
|
||||
return slot;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化槽位
|
||||
*/
|
||||
initSlot(slot, refNumber, sender, totalParts) {
|
||||
slot.inUse = true;
|
||||
slot.refNumber = refNumber;
|
||||
slot.sender = sender;
|
||||
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) {
|
||||
logger.info(`收到长短信分段 ${partNumber}/${totalParts}, 参考号: ${refNumber}`);
|
||||
|
||||
const slot = this.findOrCreateSlot(refNumber, sender, totalParts);
|
||||
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
|
||||
});
|
||||
|
||||
// 清空槽位
|
||||
this.clearSlot(slot);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空槽位
|
||||
*/
|
||||
clearSlot(slot) {
|
||||
slot.inUse = false;
|
||||
slot.parts = [];
|
||||
slot.receivedParts = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查超时
|
||||
*/
|
||||
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;
|
||||
Reference in New Issue
Block a user