# 评测 - 为您的社区添加 Discord 风格的视频和语音房间及通话 🎙

**URL:** <https://meta.discourse.org/t/resenha-add-discord-style-video-and-voice-rooms-and-calls-to-your-community/389056>\
**Category:** Plugin\
**Created:** [2025年十一月19日 16:34 UTC](https://meta.discourse.org/t/resenha-add-discord-style-video-and-voice-rooms-and-calls-to-your-community/389056 "2025-11-19T16:34:26Z")\
**Posts on this page:** 1\
**Showing post:** 69

<div class="post-metadata">

**Author:** ![nicolsdennis](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/nicolsdennis/32/545883_2.png) [@nicolsdennis](https://meta.discourse.org/u/nicolsdennis)\
**Post date:** [2026年七月20日 14:15 UTC](https://meta.discourse.org/t/resenha-add-discord-style-video-and-voice-rooms-and-calls-to-your-community/389056/69 "2026-07-20T14:15:36Z")

</div>

@Falco 嘿，我在测试更新后的 resenha 时遇到了一些可复现的问题。分享我的发现以及我本地正在运行的补丁，以防它们对上游开发有用。

**1. 管理 UI 静默丢弃 `room_type`，导致房间无法保持阶段状态**  
`app/controllers/resenha/admin_rooms_controller.rb:65`（`room_params`）在允许参数列表中省略了 `:room_type`，且 `admin_room_serializer.rb` 从未序列化它。

通过管理 UI 进行的每次阶段分配都会被丢弃，房间创建为开放状态，并在任何后续的管理编辑中“恢复”为开放状态。已通过生产日志确认；面向用户的控制器允许该参数，仅管理路径丢失了它。

```plaintext
# admin_rooms_controller.rb — 在允许列表中添加 :room_type，然后：
if permitted.key?(:room_type)
  value = Resenha::Room::ROOM_TYPES[permitted[:room_type].to_s]
  raise Discourse::InvalidParameters.new(:room_type) if value.nil?
  permitted[:room_type] = value
end
# admin_room_serializer.rb — 序列化 room_type 以便表单正确初始化

```

**2. 无效的 `room_type` 静默回退为开放状态**  
`app/controllers/resenha/rooms_controller.rb:575` — `ROOM_TYPES[...] || ROOM_TYPE_OPEN`。

在任何其他方面有效的房间编辑中，任何格式错误/过期的 `room_type` 都会悄悄地将阶段房间翻转为开放状态。返回 400 错误可以使行为不端的调用者可见，而不是损坏房间。

```plaintext
value = Resenha::Room::ROOM_TYPES[permitted[:room_type].to_s]
raise Discourse::InvalidParameters.new(:room_type) if value.nil?
permitted[:room_type] = value

```

**3. 心跳复活刚刚离开的用户（幽灵存在）**  
`rooms_controller.rb:234`（`heartbeat`）无条件地重新添加存在状态，因此当 `leave`（`:220`）处理时，正在传输中的心跳稍后到达并重新创建已离开的用户。  
该幽灵存在状态将持续到 TTL 回收，而回收不会广播——客户端显示他们“在房间内”长达一分钟。

已在设备上复现；踢出具有相同的暴露问题。

```plaintext
# ParticipantTracker: 15秒墓碑
def mark_left(room_id, user_id) = redis.setex(left_key(room_id, user_id), 15, "1")
def recently_left?(room_id, user_id) = redis.exists?(left_key(room_id, user_id))
# leave/kick → mark_left; join/livekit_token → clear_left; heartbeat:
return head :no_content if Resenha::ParticipantTracker.recently_left?(@room.id, current_user.id)

```

**4. 过期的 `participant_left` webhook 幻影踢出新鲜会话**  
`livekit_webhooks_controller.rb:37/57` — 离开仅通过用户身份匹配。  
在快速断开/重新连接时，被取代会话的 `participant_left` 晚到并过期 _新_ 会话的存在状态（用户在重新连接后约 3 秒被“踢出”，约 15 秒后返回）。  
已在设备上复现；当重新连接早于旧会话的断开时，`gone_at` 启发式方法无法区分会话。

```plaintext
# participant_joined → 记录实时 SID
Resenha::ParticipantTracker.set_livekit_sid(room.id, user_id, event.dig("participant", "sid"))
# expire_participant → 跳过被取代会话的离开
known = Resenha::ParticipantTracker.livekit_sid(room.id, user_id)
return if sid.present? && known.present? && sid != known

```

**5. 最后一个离开后 `DeleteRoom` 404 淹没日志**  
`lib/resenha/livekit/room_service_client.rb:84` 对任何非 200 响应发出警告。  
SFU 在房间清空的那一刻自动关闭房间，因此最后一个离开的 DeleteRoom 通常会与之竞争——“请求的房间不存在”是期望的最终状态，而不是故障。  
它会在每次最后离开时进入 Logster。

```plaintext
elsif method == "DeleteRoom" && response.status == 404
  Rails.logger.debug("[resenha-livekit] DeleteRoom no-op for room #{room.id}: already gone")
  true

```

如果你愿意，我可以发送一个拉取请求。

正如我之前所说，我不完全了解你的方向，但你可以看看我的，因为我在这里发了一篇帖子 → [https://meta.discourse.org/t/discourse-desktop-mac-app/406912/10?u=nicolsdennis](https://meta.discourse.org/t/discourse-desktop-mac-app/406912/10?u=nicolsdennis)

---

_[View the full topic](https://meta.discourse.org/t/resenha-add-discord-style-video-and-voice-rooms-and-calls-to-your-community/389056)._
