| | |
| | | this.timer = null; |
| | | this.started = false; |
| | | this.flushing = false; |
| | | this.flushQueued = false; |
| | | this.queuedFlushOptions = null; |
| | | } |
| | | |
| | | start() { |
| | |
| | | |
| | | const entry = this.getOrCreateEntry(device); |
| | | entry.device = device; |
| | | const now = Date.now(); |
| | | |
| | | // 首次进入脏状态时记录时间,便于控制“缓存满一轮再发送”。 |
| | | if (!entry.dirty) { |
| | | entry.dirtySinceAt = now; |
| | | } |
| | | |
| | | entry.dirty = true; |
| | | entry.lastUpdateAt = Date.now(); |
| | | entry.lastUpdateAt = now; |
| | | |
| | | if (this.includeDeviceIdField) { |
| | | entry.payload[this.deviceIdField] = device.deviceId; |
| | |
| | | device, |
| | | payload: {}, |
| | | dirty: false, |
| | | dirtySinceAt: 0, |
| | | firstSeenAt: Date.now(), |
| | | lastUpdateAt: 0, |
| | | lastFlushAt: 0, |
| | |
| | | |
| | | this.timer = setTimeout(() => { |
| | | this.flush({ reason: 'timer' }).catch((error) => { |
| | | this.logger.error(`[APP] Aggregator flush failed: ${error.message}`); |
| | | this.logger.error(`[APP] 聚合器刷新失败: ${error.message}`); |
| | | }); |
| | | }, this.computeDelayMs()); |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | mergeQueuedFlushOptions(options = {}) { |
| | | const nextOptions = { |
| | | reason: options.reason || 'manual', |
| | | deviceId: options.deviceId || '', |
| | | }; |
| | | |
| | | if (!this.queuedFlushOptions) { |
| | | this.queuedFlushOptions = nextOptions; |
| | | return; |
| | | } |
| | | |
| | | const currentOptions = this.queuedFlushOptions; |
| | | const currentIsTimer = currentOptions.reason === 'timer' || currentOptions.reason === 'queued'; |
| | | const nextIsTimer = nextOptions.reason === 'timer' || nextOptions.reason === 'queued'; |
| | | |
| | | if (currentIsTimer && !nextIsTimer) { |
| | | currentOptions.reason = nextOptions.reason; |
| | | } |
| | | |
| | | if (!currentOptions.deviceId || !nextOptions.deviceId || currentOptions.deviceId !== nextOptions.deviceId) { |
| | | currentOptions.deviceId = ''; |
| | | } |
| | | } |
| | | |
| | | async flush(options = {}) { |
| | | const reason = options.reason || 'manual'; |
| | | const deviceId = options.deviceId || ''; |
| | | |
| | | if (this.flushing) { |
| | | this.flushQueued = true; |
| | | this.mergeQueuedFlushOptions({ reason, deviceId }); |
| | | return false; |
| | | } |
| | | |
| | |
| | | return false; |
| | | } |
| | | |
| | | // 定时触发时要求数据至少缓存满一个周期,避免刚收到就被单独发出。 |
| | | if ((reason === 'timer' || reason === 'queued') && entry.dirtySinceAt > 0) { |
| | | const dirtyAgeMs = Date.now() - entry.dirtySinceAt; |
| | | |
| | | if (dirtyAgeMs < this.flushIntervalMs) { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | return true; |
| | | }); |
| | | |
| | | for (const entry of entries) { |
| | | const flushUpdateAt = entry.lastUpdateAt; |
| | | const payload = { |
| | | ...entry.payload, |
| | | }; |
| | |
| | | continue; |
| | | } |
| | | |
| | | entry.dirty = false; |
| | | if (entry.lastUpdateAt === flushUpdateAt) { |
| | | entry.dirty = false; |
| | | entry.dirtySinceAt = 0; |
| | | } |
| | | |
| | | entry.lastFlushAt = Date.now(); |
| | | } catch (error) { |
| | | this.logger.error(`[APP] Aggregator device flush failed deviceId=${entry.device.deviceId}: ${error.message}`); |
| | | this.logger.error(`[APP] 聚合器设备刷新失败 deviceId=${entry.device.deviceId}: ${error.message}`); |
| | | } |
| | | } |
| | | |
| | |
| | | this.scheduleNextFlush(); |
| | | } |
| | | |
| | | if (this.flushQueued) { |
| | | this.flushQueued = false; |
| | | await this.flush({ reason: 'queued' }); |
| | | if (this.queuedFlushOptions) { |
| | | const queuedOptions = this.queuedFlushOptions; |
| | | this.queuedFlushOptions = null; |
| | | await this.flush(queuedOptions); |
| | | } |
| | | } |
| | | } |