fix(weixin): 扫码QR超时自动重发——修'二维码过期'(旧码被幂等返回)
StartLogin 幂等会一直返回同一二维码,但微信bot绑定码有效期极短;此前测试触发过一次登录 会话,旧码过了有效期,用户刷新仍拿到它→扫即'过期'。修: loginSession 加 startedAt, 超过 qrRefreshTTL(90s) 时 StartLogin 取消旧会话并重发新鲜二维码(panel'刷新二维码'永远可扫)。 重启清除内存中旧会话。build/test绿,已部署.
This commit is contained in:
1 parent
36bd19e997
commit
a9dfae4d5f
1 file changed
+23
-7
@@ -8,6 +8,7 @@ package weixin_ilink
|
|||||||
// concurrent QR binds.
|
// concurrent QR binds.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -41,14 +42,20 @@ type LoginQR struct {
|
|||||||
|
|
||||||
// loginSession tracks one scan-login run. It is guarded by Adapter.loginMu.
|
// loginSession tracks one scan-login run. It is guarded by Adapter.loginMu.
|
||||||
type loginSession struct {
|
type loginSession struct {
|
||||||
qrcode string
|
qrcode string
|
||||||
image string
|
image string
|
||||||
|
startedAt time.Time
|
||||||
// cancel stops the poll goroutine early (e.g. Connect's deadline elapsed).
|
// cancel stops the poll goroutine early (e.g. Connect's deadline elapsed).
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
// done carries the login outcome; the poll goroutine writes exactly once.
|
// done carries the login outcome; the poll goroutine writes exactly once.
|
||||||
done chan error
|
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
|
// 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
|
// 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
|
// 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
|
return LoginQR{}, ErrAlreadyLoggedIn
|
||||||
}
|
}
|
||||||
if sess := a.loginSession; sess != nil {
|
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)
|
raw, err := a.fetchQRCode(ctx)
|
||||||
@@ -74,10 +89,11 @@ func (a *Adapter) StartLogin(ctx context.Context) (LoginQR, error) {
|
|||||||
|
|
||||||
sessCtx, cancel := context.WithCancel(context.Background())
|
sessCtx, cancel := context.WithCancel(context.Background())
|
||||||
sess := &loginSession{
|
sess := &loginSession{
|
||||||
qrcode: raw.Qrcode,
|
qrcode: raw.Qrcode,
|
||||||
image: raw.QrcodeImgContent,
|
image: raw.QrcodeImgContent,
|
||||||
cancel: cancel,
|
startedAt: time.Now(),
|
||||||
done: make(chan error, 1),
|
cancel: cancel,
|
||||||
|
done: make(chan error, 1),
|
||||||
}
|
}
|
||||||
a.loginSession = sess
|
a.loginSession = sess
|
||||||
a.setLoginState(LoginStatusWaiting, "")
|
a.setLoginState(LoginStatusWaiting, "")
|
||||||
|
|||||||
Reference in new issue
Block a user