refactor(weixin-ilink): 措辞纠正 —「会话过期」→「会话被 iLink 拒绝」(零逻辑改动)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

事实(主人 2026-10-03 确认):iLink 的 bot token 是长期凭据(一个月前扫码的仍可用);errcode=-14 是 iLink 侧拒绝/作废该会话,不是时间性过期;且至今未观测到真实 -14(当日全部日志 0 次)。此前代码把这些写成「过期」,属沿用的旧命名,现纠正。
- 纯改名/文案:ErrSessionExpired→ErrSessionRejected、errSessionExpired→errcodeSessionRejected、expired/expiredToken→rejected/rejectedToken、markExpired/expiredLocked/expiredState→markRejected/rejectedLocked/rejectedState;409 错误码 session_expired→session_rejected
- 行为一字未改:显式标记 / Connect 不假 OK / 面板 needsRescan+lastError / 扫码或换新 token 即清除 全部保留
- 微信协议自身的「二维码过期」与 auth 的 JWT/refresh 过期一律未动(那些是真·时间性过期)
- Docs/api-client v0.39 补事实说明;顺手收尾 3 处残留注释(admin.go / channelbridge/inbound.go / readloop_test.go)
This commit is contained in:
AskaEth committed 2026-10-03 18:26:27 +08:00
1 parent cdc370dc82
commit a51aadeef4
14 files changed
+205 -192

No files matched your search

+9 -9
View File
@@ -145,7 +145,7 @@ func (a *Adapter) runLogin(sess *loginSession, sessCtx context.Context) {
// completeLogin persists the confirmed credentials and marks the session live.
// It writes cfg under a.mu, then clears the session under loginMu (never nesting
// the two locks), and finally signals the waiting Connect path. A confirmed scan
// delivers a fresh bot_token, which is the canonical way OUT of the expired state
// delivers a fresh bot_token, which is the canonical way OUT of the rejected state
// (session_state.go): the rescan flag is cleared here.
func (a *Adapter) completeLogin(sess *loginSession, status qrcodeStatusResp) {
a.mu.Lock()
@@ -175,14 +175,14 @@ func (a *Adapter) completeLogin(sess *loginSession, status qrcodeStatusResp) {
// completeBindedRedirect treats binded_redirect as an already-bound outcome: the
// scanned bot is already linked to this instance, so the server issues no new
// credentials and any existing token remains valid. Because NO new token arrives,
// an expired session stays expired here — binded_redirect carries no proof that a
// a rejected session stays rejected here — binded_redirect carries no proof that a
// token iLink already rejected with errcode=-14 works again, so the adapter does
// not claim a live session on the strength of it. A confirmed scan (completeLogin)
// is what clears the expired state.
// is what clears the rejected state.
func (a *Adapter) completeBindedRedirect(sess *loginSession) {
a.log.Info("weixin_ilink: login already bound (binded_redirect)")
a.mu.Lock()
a.connected = a.cfg.BotToken != "" && !a.expiredLocked()
a.connected = a.cfg.BotToken != "" && !a.rejectedLocked()
a.loginStatus = LoginStatusConfirmed
a.loginDetail = loginBindedRedirectDetail
a.mu.Unlock()
@@ -218,12 +218,12 @@ func (a *Adapter) failLogin(sess *loginSession, err error) {
// localTokenList returns the locally-held bot tokens to advertise on
// get_bot_qrcode, so an already-bound bot can be detected (binded_redirect). On a
// fresh bind it returns an empty (non-nil) slice so the wire carries `[]` rather
// than `null`, matching the official client. An EXPIRED token is deliberately not
// advertised: it is not a usable bind, and offering it lets the server answer
// binded_redirect (no new credentials) — which would leave the owner stuck with a
// dead token and no way back. Reporting `[]` asks for a real rebind.
// than `null`, matching the official client. A token iLink REJECTED is deliberately
// not advertised: it is not a usable bind, and offering it lets the server answer
// binded_redirect (no new credentials) — which would leave the owner stuck with an
// unusable credential and no way back. Reporting `[]` asks for a real rebind.
func (a *Adapter) localTokenList() []string {
if expired, _ := a.expiredState(); expired {
if rejected, _ := a.rejectedState(); rejected {
return []string{}
}
a.mu.RLock()