makecindy / makecindy/cindy

[Bug] 同一个飞书 Bot 未正确阻止多台设备同时连接

Open
#18 1 comment 0 reactions 0 assignees View on GitHub
bug
Dominant language
TypeScript
Stars
2.7k
Forks
395
Avg merge
21h 48m
Merged PRs (30d)
776

Description

## 问题描述

同一组飞书 Bot App ID 和 App Secret 可以同时配置到两台设备。两台设备均显示连接成功,没有出现设备冲突提示。

测试信息:

- 测试日期:2026-07-16
- 测试用例:TC-06-15-04
- 测试环境:双设备
- 复现情况:稳定复现

## 复现步骤

1. 在设备 A 配置飞书 Bot,确认状态显示“已连接”。
2. 在设备 B 配置相同的 App ID 和 App Secret。
3. 等待设备 B 完成连接。
4. 分别检查两台设备的飞书 Bot 状态。

## 实际结果

两台设备均显示“已连接”,设备 B 没有收到冲突提示,设备 A 的状态也没有变化。

## 期望结果

同一个飞书 Bot 同一时间只能绑定一台设备。

设备 A 已连接时,设备 B 再配置相同 Bot,应显示“已被另一台设备使用”,停止本机 WebSocket 连接,且不能展示连接成功 Toast 或已连接卡片。

## 当前代码分析

当前冲突检测位于 `packages/lizi-im/src/feishu/conflictDetector.ts`,采用一次性的本地启发式判断:

- 首次收到 `ws client ready` 后立即将结果确定为 `connected`。
- 只有在 8 秒内没有收到 ready,同时累计至少两次 reconnect,才会判定为 `conflict`。
- 一旦结果已经确定为 `connected`,后续 reconnect 或连接上限错误不能再将状态改为 `conflict`。
- 飞书返回 `1000040350 / exceed_conn_limit` 时,当前代码调用的是 `markError()`,最终进入 `error`,不会广播 `feishuBot:conflict`。
- 客户端保存凭证后直接连接飞书,没有携带本机 deviceId,也没有跨设备的 Bot 占用记录。

最小状态机验证结果:

```text
ready 后再出现 reconnect -> connected
收到 exceed_conn_limit -> error
未 ready 且出现两次 reconnect -> conflict
```

## 根因

当前实现依赖单台客户端观察飞书 SDK 日志,无法在飞书允许多个连接或冲突信号晚于 ready 时判断同一个 Bot 是否已经被另一台设备使用,因此两台设备可以同时进入并保持 `connected`。

## 实现建议

要保证两台设备不能同时显示连接成功,需要增加跨设备仲裁:

1. 服务端按 Bot App ID 保存当前占用设备,使用 deviceId 标识设备,并通过原子 claim、租约和心跳维护占用状态。
2. 客户端在启动飞书 WebSocket 前申请占用权。申请失败时进入 `conflict`,广播冲突事件并确保本机连接已停止。
3. 清除凭证、退出应用或租约超时后释放占用权。
4. 将 `1000040350 / exceed_conn_limit` 明确映射为 `conflict`,作为飞书侧拒绝连接时的兜底。
5. 手工填写凭证、扫码注册、应用启动自动连接应复用同一套仲裁流程。
6. 服务端改动在独立的 `cindy-server` 仓库完成,并与客户端改动同步发布。

## 验收标准

- 设备 A 已连接时,设备 B 配置相同 Bot 后显示设备冲突。
- 设备 B 不显示连接成功 Toast、状态或已连接卡片。
- 冲突设备的 WebSocket 已停止,不继续接收或发送 Bot 消息。
- 设备 A 保持正常连接。
- 设备 A 主动解绑或租约过期后,设备 B 可以重新连接。
- 手工配置、扫码注册和应用启动自动连接均符合上述行为。
- 补充冲突检测和跨设备占用的回归测试,至少覆盖 ready 后出现冲突、`exceed_conn_limit`、租约释放和自动重连场景。

Contributor guide

Open the contributing guide

Research direction

Start with packages/lizi-im/src/feishu/conflictDetector.ts and trace the manual credential, QR registration, and startup auto-connect entry points. Then inspect the separate cindy-server repository for the device claim, lease, heartbeat, and release flow. Done means one Bot can be active on only one device, conflicts stop the local WebSocket without success UI, and regression coverage includes late conflicts, exceed_conn_limit, lease release, and reconnect.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
backend, distributed-systems
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
32/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.