feat(weixin-ilink,gateway): 会话过期显式化 — 不再静默变哑/假 OK 重连,面板可见「需重扫」
- 适配器新增过期状态机(plugins/channels/weixin_ilink/session_state.go):getupdates/sendmessage/getuploadurl 收到 errcode=-14 即 markExpired(ERROR 仅一次,只带 bot_id,token 永不入日志),扫码登录拿到新 token 或配置换新 token 时清除(惰性比对) - Connect/HealthCheck 在过期时返回 ErrSessionExpired(可与 ErrNotConnected 区分):不再对死 token 设 connected=true 报假成功;发送路径同样返回该错误而不是笼统 ErrNotConnected - 网关:POST /admin/channels/open 遇过期 → 409 session_expired + 明确文案(不再吞错回 200);GET /admin/channels 摘要新增 needsRescan/lastError(经可选接口类型断言读取,取不到不伪造) - channelbridge:读循环非取消退出时把自身记账的 connected 置 false(热重载被替换者退出不抹掉新实例) - login/login_admin:过期时 StartLogin 允许重新出码、LoginStatus 如实报 failed(原先可能把死 token 当 confirmed 重新落盘) - Docs/api-client v0.39;新增 5 组测试 + 变异验证(去掉标记/恢复假成功/网关不填字段 均 FAIL)
This commit is contained in:
1 parent
2092094d0a
commit
cdc370dc82
14 files changed
+975
-30
No files matched your search
@@ -119,8 +119,11 @@ func (a *Adapter) StartLogin(ctx context.Context) (LoginQR, error) {
|
||||
a.loginMu.Lock()
|
||||
defer a.loginMu.Unlock()
|
||||
|
||||
// Already bound → scanning is moot.
|
||||
if a.hasToken() {
|
||||
// Already bound → scanning is moot — unless the stored token is EXPIRED: iLink
|
||||
// rejected it with errcode=-14, so it is not a usable bind and the owner must
|
||||
// be able to re-scan (otherwise the only way out of the expired state would be
|
||||
// unreachable). A live token still short-circuits the QR flow as before.
|
||||
if expired, _ := a.expiredState(); a.hasToken() && !expired {
|
||||
return LoginQR{}, ErrAlreadyLoggedIn
|
||||
}
|
||||
if sess := a.loginSession; sess != nil {
|
||||
@@ -178,11 +181,23 @@ func (a *Adapter) StartLogin(ctx context.Context) (LoginQR, error) {
|
||||
|
||||
// LoginStatus reports the current scan-login state. A non-empty bot token
|
||||
// overrides to "confirmed" even if a stale session is still recorded, so the panel
|
||||
// always sees the true binding. detail provides the bot id on confirmation and the
|
||||
// failure reason on failure.
|
||||
// always sees the true binding — except when that token is EXPIRED: iLink rejected
|
||||
// it with errcode=-14, so reporting "confirmed" would make the panel re-persist a
|
||||
// dead credential and call the bind a success. An expired bind is reported as
|
||||
// failed with the re-scan reason instead, while a re-scan already in progress
|
||||
// keeps its own state. detail provides the bot id on confirmation and the failure
|
||||
// reason on failure.
|
||||
func (a *Adapter) LoginStatus(ctx context.Context) (status, detail string) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if a.expiredLocked() {
|
||||
if a.loginStatus == LoginStatusWaiting || a.loginStatus == LoginStatusNeedVerifyCode {
|
||||
// A re-scan the owner already started: report its progress, not the
|
||||
// expired state it is meant to replace.
|
||||
return a.loginStatus, a.loginDetail
|
||||
}
|
||||
return LoginStatusFailed, a.rescanDetail
|
||||
}
|
||||
if a.cfg.BotToken != "" {
|
||||
return LoginStatusConfirmed, a.cfg.BotID
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user