# 企微账号登录与席位决策流程 ## 计数模型 - 客户订阅席位按唯一企微账号 `corpId + userId` 计数。 - 多台电脑使用同一 Fmode 凭据连接同一企微账号时,共享服务端账号会话,不重复占客户席位。 - 上游登录实例容量与客户订阅席位分开管理。 - 临时二维码、待验证码和待席位决策的登录尝试不计客户席位,并设置过期清理时间。 ## 完整流程 ```mermaid flowchart TD A["启动客户端或打开登录页"] --> B{"Fmode Token 有效"} B -- "否" --> B1["配置或刷新 Token"] --> B B -- "是" --> C["查询订阅状态"] C --> D{"订阅有效"} D -- "否" --> D1["开通或续费"] --> C D -- "是" --> E["查询已绑定账号"] E --> F{"选择登录方式"} F -- "已有账号在线" --> S["绑定当前电脑并共享会话"] F -- "已有账号离线" --> G["复用账号 guid 生成二维码"] F -- "登录其他账号" --> H["创建临时 uid 和上游实例"] G --> Q["扫码与验证码状态轮询"] H --> Q Q --> I{"登录成功并获得 corpId + userId"} I -- "身份已绑定" --> R["复用席位并更新 canonical guid"] --> S I -- "新身份" --> J{"客户席位有空余"} J -- "是" --> K["正式绑定新账号"] --> S J -- "否" --> L{"购买、替换或取消"} L -- "新增购买席位" --> M["实时询价并幂等扣费"] M --> N["重新查询席位并提交登录"] --> K L -- "替换已有账号" --> O["确认目标账号并停止旧 guid"] --> K L -- "取消" --> P["停止临时实例并清理"] S --> T["登录完成并启动监听或 Relay"] ``` ## 接口约定 | 接口 | 用途 | | --- | --- | | `GET /login/accounts` | 返回当前租户已绑定账号、在线状态和席位摘要 | | `POST /login/select` | 将当前电脑 uid 绑定到已有账号 | | `POST /login/start` | 创建或复用上游登录会话;`newAccount=true` 时创建临时 uid | | `POST /login/check` | 轮询二维码;成功后按账号身份去重并返回席位决策 | | `POST /login/resolve` | 执行购买后提交、替换或取消 | | `GET /subscribe/status?seats=N&months=1` | 获取增购实时补差报价 | | `POST /subscribe` | 使用报价金额和幂等键完成增购 | ## 席位已满决策 扫码身份为新账号且席位已满时,`/login/check` 返回: ```json { "status": 2, "decisionRequired": true, "loginState": "decision_required", "actions": ["purchase", "replace", "cancel"] } ``` - `purchase`:按当前席位数加一询价并购买;购买成功后再次加锁校验,随后自动绑定当前扫码账号。 - `replace`:客户选择一个已绑定账号,二次确认后停止旧会话并绑定新账号。 - `cancel`:停止临时会话并清理登录尝试。 支付和账号绑定使用相同租户锁串行化。支付成功但绑定请求中断时,客户端复用原幂等订单并再次执行 `purchase` 提交,避免重复扣费。 ## 兼容规则 - 旧 `QiweiDevice` 记录存在 `guid + lastLoginAt` 时,懒迁移为一个 legacy 账号席位。 - 只有二维码但从未成功登录的旧记录不占客户席位。 - 业务接口继续使用 uid,服务端通过 uid 找到共享 guid,现有 MCP 工具调用保持兼容。