kt-template-online-api/docs/specs/2026-06-13-qqbot-napcat-login-device-persistence-design.md

113 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# QQBot NapCat 登录与设备持久化设计
## 背景
当前 QQBot 的 NapCat 登录链路存在两个耦合问题:
- Docker 重建容器时只持久化 `/app/.config/QQ`,没有固定 MAC、hostname 和 `/etc/machine-id`QQ 侧容易把同一账号识别成新设备。
- 密码登录或验证码登录返回 `needNewDevice` 后,后端只把 `jumpUrl` 透给 Admin没有按 NapCat 上游流程继续执行新设备二维码获取、轮询和确认登录。
上游依据:
- NapCat Docker 官方 compose 固定 `mac_address`,并挂载 QQ 数据目录到 `/app/.config/QQ`
- NapCat 文档说明 Docker 环境中的 QQ 数据目录位于 `/app/.config/QQ`
- NapCat 源码中 Linux GUID 与 `/etc/machine-id` 和 MAC 相关。
- NapCat WebUI 的新设备流程为 `GetNewDeviceQRCode -> PollNewDeviceQR -> NewDeviceLogin`
## 目标
本次改动以一次完整闭环为目标:
- 重建 NapCat 容器时保持稳定设备身份。
- 将快速登录、密码登录、验证码、新设备验证收敛为后端登录状态机。
- SSE 向 Admin 展示每一步中文进度和必要操作,不暴露内部 token。
- 线上完成一次当前账号的实际登录链路验证。
## 非目标
- 不绕过 QQ 安全验证,不做验证码自动求解。
- 不删除现有 QQ 登录数据,除非用户明确执行重置登录态。
- 不把账号在线状态和容器在线状态合并为同一个状态字段。
- 不新增独立 NapCat 部署方式。
## 运行时设备持久化
每个托管 NapCat 容器的数据目录维护以下持久文件:
- `$DATA_DIR/QQ`:继续挂载到 `/app/.config/QQ`
- `$DATA_DIR/device.env`:保存 `NAPCAT_MAC_ADDRESS``NAPCAT_HOSTNAME`
- `$DATA_DIR/machine-id`:挂载到 `/etc/machine-id:ro`
容器创建或重建时:
1. 如果同名旧容器仍存在,优先读取旧容器的 MAC、hostname 和 `/etc/machine-id`
2. 如果 `$DATA_DIR/device.env``$DATA_DIR/machine-id` 已存在,优先复用持久文件。
3. 如果没有可复用值,则基于容器名生成稳定的本地管理 MAC、hostname 和 32 位 machine-id。
4. `docker run` 固定 `--mac-address`、`--hostname`,并挂载 machine-id 文件。
重置登录态只清理 `$DATA_DIR/QQ` 下的登录数据,不删除 `device.env``machine-id`
## 登录状态机
后端登录链路按固定顺序推进:
1. 快速登录:有历史会话时优先使用 `ACCOUNT/-q`。失败后进入密码登录,不清设备数据。
2. 密码登录:使用账号密码计算 MD5调用 NapCat `PasswordLogin`
3. 验证码阶段:`needCaptcha` 时 SSE 推送验证码 URLAdmin 提交用户完成验证后的 `ticket/randstr/sid`
4. 新设备阶段:`needNewDevice` 时后端保存 `jumpUrl``newDevicePullQrCodeSig`,随后调用 `GetNewDeviceQRCode`
5. 新设备轮询:后端使用 `PollNewDeviceQR` 轮询扫码状态。
6. 新设备确认:扫码确认后调用 `NewDeviceLogin`,成功后检查登录态、清理临时密码环境、绑定账号。
如果 `NewDeviceLogin` 再次返回 `needNewDevice`,状态机重新进入新设备阶段。
## SSE 与 Admin 表现
SSE 保留最近事件刷新页面可以恢复当前阶段。Admin 只展示用户需要知道的状态:
- 正在尝试快速登录
- 快速登录失败,尝试密码登录
- 密码登录需要 QQ 安全验证
- QQ 需要新设备验证
- 新设备二维码已生成
- 已扫码,等待确认
- 新设备确认中
- 登录成功
- 登录失败及原因
Admin 不直接展示或保存 NapCat 内部轮询 token。二维码继续复用现有扫码区域展示。
## 错误处理
- 密码环境清理失败必须阻断成功,不允许把登录成功和清理失败混在一起。
- 普通 WebSocket close 不清空已有明确下线原因。
- `isOnline:false` 只能表示账号离线,不能表示容器离线。
- 新设备二维码过期时保持 session pending并允许刷新当前阶段二维码。
- 重建容器失败时保留旧 QQ 数据目录和设备持久文件,错误写入账号或容器状态。
## 验证计划
后端单测:
- Docker 创建脚本包含稳定 `device.env`、`machine-id`、`--mac-address`、`--hostname` 和 QQ 数据挂载。
- 登录服务覆盖 `CaptchaLogin -> needNewDevice -> GetNewDeviceQRCode -> PollNewDeviceQR -> NewDeviceLogin`
- 新设备阶段刷新状态不会误判登录成功,也不会提前清理密码环境。
本地验证:
- 运行相关 Jest 单测。
- 运行类型检查。
线上验证:
- 推送后观察 Jenkins、K8s rollout、Pod 日志。
- 当前账号执行更新登录,确认 SSE 进度包含验证码和新设备阶段。
- 重建同一 NapCat 容器后检查 MAC、hostname、machine-id、QQ 数据目录仍复用同一持久值。
- 登录成功后确认容器环境不再残留临时 `NAPCAT_QUICK_PASSWORD`
## 完成标准
- 代码、测试、文档和线上验证证据完整。
- 线上账号完成一次可用登录闭环。
- 不再因普通容器重建导致同一账号反复被识别为新设备。
- KT workflow 记录本次稳定问题点和解决方案。