From a9dfae4d5fa03fee8729b0ac30330dc7ae6c7290 Mon Sep 17 00:00:00 2001 From: Aska Date: Mon, 7 Sep 2026 21:41:43 +0800 Subject: [PATCH] =?UTF-8?q?fix(weixin):=20=E6=89=AB=E7=A0=81QR=E8=B6=85?= =?UTF-8?q?=E6=97=B6=E8=87=AA=E5=8A=A8=E9=87=8D=E5=8F=91=E2=80=94=E2=80=94?= =?UTF-8?q?=E4=BF=AE'=E4=BA=8C=E7=BB=B4=E7=A0=81=E8=BF=87=E6=9C=9F'(?= =?UTF-8?q?=E6=97=A7=E7=A0=81=E8=A2=AB=E5=B9=82=E7=AD=89=E8=BF=94=E5=9B=9E?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit StartLogin 幂等会一直返回同一二维码,但微信bot绑定码有效期极短;此前测试触发过一次登录 会话,旧码过了有效期,用户刷新仍拿到它→扫即'过期'。修: loginSession 加 startedAt, 超过 qrRefreshTTL(90s) 时 StartLogin 取消旧会话并重发新鲜二维码(panel'刷新二维码'永远可扫)。 重启清除内存中旧会话。build/test绿,已部署. --- plugins/channels/weixin_ilink/login_admin.go | 30 +++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/plugins/channels/weixin_ilink/login_admin.go b/plugins/channels/weixin_ilink/login_admin.go index 50d9ced..265439a 100644 --- a/plugins/channels/weixin_ilink/login_admin.go +++ b/plugins/channels/weixin_ilink/login_admin.go @@ -8,6 +8,7 @@ package weixin_ilink // concurrent QR binds. import ( + "time" "context" "errors" "fmt" @@ -41,14 +42,20 @@ type LoginQR struct { // loginSession tracks one scan-login run. It is guarded by Adapter.loginMu. type loginSession struct { - qrcode string - image string + qrcode string + image string + startedAt time.Time // cancel stops the poll goroutine early (e.g. Connect's deadline elapsed). cancel context.CancelFunc // done carries the login outcome; the poll goroutine writes exactly once. done chan error } +// qrRefreshTTL bounds how long a WeChat bot QR is usable before StartLogin re-mints +// a fresh one. iLink bot QRs are short-lived; an idempotent StartLogin must not keep +// serving a long-expired QR (the user would scan it and get "二维码过期"). +const qrRefreshTTL = 90 * time.Second + // StartLogin begins a scan-login and returns the QR code to display. It is // idempotent: when a login is already in progress it returns the current QR // without issuing a new get_bot_qrcode, and when a bot token is already present it @@ -63,7 +70,15 @@ func (a *Adapter) StartLogin(ctx context.Context) (LoginQR, error) { return LoginQR{}, ErrAlreadyLoggedIn } if sess := a.loginSession; sess != nil { - return LoginQR{Qrcode: sess.qrcode, Image: sess.image}, nil + // Idempotent for a fresh QR, but re-mint once the current QR has aged past + // qrRefreshTTL so the panel's 刷新二维码 always yields a scannable code. + if time.Since(sess.startedAt) < qrRefreshTTL { + return LoginQR{Qrcode: sess.qrcode, Image: sess.image}, nil + } + if sess.cancel != nil { + sess.cancel() + } + a.loginSession = nil } raw, err := a.fetchQRCode(ctx) @@ -74,10 +89,11 @@ func (a *Adapter) StartLogin(ctx context.Context) (LoginQR, error) { sessCtx, cancel := context.WithCancel(context.Background()) sess := &loginSession{ - qrcode: raw.Qrcode, - image: raw.QrcodeImgContent, - cancel: cancel, - done: make(chan error, 1), + qrcode: raw.Qrcode, + image: raw.QrcodeImgContent, + startedAt: time.Now(), + cancel: cancel, + done: make(chan error, 1), } a.loginSession = sess a.setLoginState(LoginStatusWaiting, "")