活跃用户状态的更新时机

非常好,这是一个关键问题,因为它直接关系到我们这个系统的实时性和准确性。

在我们最终实现的**“基于客户端消息历史推断”的方案中,用户状态的更新时机被设计得非常巧妙和全面,主要分为以下三种类型**:


1. 事件驱动的实时更新 (Event-Driven Real-time Updates)

这是最主要的更新方式,确保了用户操作能够立即得到反馈。

  • 加载历史消息后 (fetchHistoryMessages):

    • 时机: 应用启动,成功连接WebSocket后,会立即请求并加载所有历史消息。

    • 动作: 当所有历史消息被加载到全局的 allMessages 数组后,会立即触发一次 updateUIFromMessages()

    • 目的: 这是为了构建初始的用户状态。根据所有历史记录,系统能立刻知道哪些用户在过去5分钟内是活跃的,并渲染出初始的用户列表和统计信息。

  • 接收到新聊天消息时 (onSocketMessage - chat类型):

    • 时机: WebSocket接收到一条新的聊天消息。

    • 动作:

      1. 新消息被添加到 allMessages 数组的末尾。

      2. 调用 appendChatMessage() 将消息渲染到聊天窗口。

      3. 立即触发一次 updateUIFromMessages()

    • 目的: 确保当一个用户(无论是自己还是他人)发送消息后,其“活跃”状态瞬间更新,并立刻反映在所有UI上。

  • 删除消息时 (onSocketMessage - delete类型):

    • 时机: WebSocket接收到一个删除消息的指令。

    • 动作:

      1. 对应的消息从 allMessages 数组中被移除。

      2. DOM中对应的消息元素被删除。

      3. 立即触发一次 updateUIFromMessages()

    • 目的: 如果被删除的是某个用户在5分钟内的最后一条消息,这次更新会重新计算他的活跃状态,他可能会因此变为“离线”。这保证了状态的准确性。

2. 周期性的状态衰减更新 (Periodic State-Decay Updates)

这是为了处理“不活跃”状态的必要机制。

  • 定时器触发 (setInterval):

    • 时机: 在 onSocketOpen 中,我们设置了一个每15秒执行一次的定时器。

    • 动作: 每隔15秒,无条件地触发一次 updateUIFromMessages()

    • 目的: 这个机制至关重要,它解决了**“用户如何从活跃变为不活跃”**的问题。例如,一个用户在 10:00 发了一条消息,他变为“活跃”。如果在接下来的5分钟内他没有再发言,到了 10:05,他就应该变为“离线”。如果没有新消息事件来触发更新,他的状态将永远是“活跃”。这个定时器确保了系统会周期性地“唤醒”并重新检查所有用户的 lastSeen 时间,将那些已经“过期”的用户正确地从活跃列表中移除。

总结图表

| 更新时机 (Trigger) | 动作 (Action) | 目的 (Purpose) |

| ----------------------------- | -------------------------------------------------- | -------------------------------------------------- |

| 应用启动/历史加载完成 | updateUIFromMessages() 被调用一次 | 建立整个应用的初始用户状态基线。 |

| 收到新聊天消息 | allMessages.push -> updateUIFromMessages() | 即时反映用户的“活跃”行为,确保实时性。 |

| 删除消息 | allMessages.filter -> updateUIFromMessages() | 保证在撤销活跃行为后,用户状态能被正确回滚。 |

| 每15秒定时器 | updateUIFromMessages() 被调用一次 | 处理状态衰减,将长时间不发言的用户标记为不活跃。 |

通过这套“事件驱动 + 周期性衰减”的组合拳,我们的用户状态系统做到了既能对用户的实时操作做出即时响应,又能对用户的“无操作”状态进行准确的维护,从而确保了整个系统的状态始终是最新且正确的。