refactor(weixin-ilink): 措辞纠正 —「会话过期」→「会话被 iLink 拒绝」(零逻辑改动)
事实(主人 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:
1 parent
cdc370dc82
commit
a51aadeef4
14 files changed
+205
-192
No files matched your search
@@ -24,7 +24,7 @@ import (
|
||||
// messages alone.
|
||||
//
|
||||
// When the read path returns with readCtx still alive, the adapter stopped
|
||||
// reading on its own — today that is the weixin-iLink expired session
|
||||
// reading on its own — today that is the weixin-iLink rejecting the session
|
||||
// (errcode=-14, see plugins/channels/weixin_ilink/session_state.go), which used to
|
||||
// leave the bridge claiming the channel was connected while it could no longer
|
||||
// deliver anything. A cancelled readCtx is a deliberate stop (bridge Stop or a
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
// deadSessionAdapter mimics a channel whose session dies while the bridge is
|
||||
// running — today the weixin-iLink expired bot token (iLink errcode=-14): Connect
|
||||
// running — today the iLink session rejection (errcode=-14): Connect
|
||||
// reports success, the read loop then returns on its own (nothing cancelled it)
|
||||
// and IsConnected reports false from that point on.
|
||||
type deadSessionAdapter struct {
|
||||
|
||||
@@ -178,7 +178,7 @@ func (s *Server) adminListChannels(w http.ResponseWriter, r *http.Request) {
|
||||
continue
|
||||
}
|
||||
sum := ChannelSummary{Name: platform, Connected: ch.IsConnected()}
|
||||
// Optional rescan capability (weixin-iLink expired token, v0.39): reported
|
||||
// Optional rescan capability (weixin-iLink rejected session, v0.39): reported
|
||||
// only when the adapter really asks for a re-scan, so a healthy adapter
|
||||
// keeps the fields omitted rather than shipping a fabricated value.
|
||||
if ra, ok := asRescanAware(cap.Value); ok {
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
|
||||
// rescanStubAdapter is a channel adapter that also exposes the optional rescan
|
||||
// capability, exactly as *weixin_ilink.Adapter does once iLink answers
|
||||
// errcode=-14 (expired bot token, re-scan required).
|
||||
// errcode=-14 (session rejected, re-scan required).
|
||||
type rescanStubAdapter struct {
|
||||
name string
|
||||
connected bool
|
||||
@@ -58,12 +58,12 @@ var (
|
||||
)
|
||||
|
||||
// TestAdminChannelsReportsNeedsRescan asserts the panel can tell "needs a fresh
|
||||
// scan" apart from plain "not connected": the expired adapter's row carries
|
||||
// scan" apart from plain "not connected": the rejected adapter's row carries
|
||||
// needsRescan + the reason, and a healthy adapter omits both rather than shipping
|
||||
// a fabricated value.
|
||||
func TestAdminChannelsReportsNeedsRescan(t *testing.T) {
|
||||
t.Parallel()
|
||||
const reason = "iLink getupdates 返回 errcode=-14(bot token 已过期)"
|
||||
const reason = "iLink getupdates 返回 errcode=-14(本次会话已被 iLink 拒绝)"
|
||||
caps := capreg.New()
|
||||
// Registered through the wrapper, the way the composition root registers it.
|
||||
if err := caps.Register(capreg.Capability{
|
||||
@@ -96,10 +96,10 @@ func TestAdminChannelsReportsNeedsRescan(t *testing.T) {
|
||||
t.Fatalf("no weixin-ilink row in %+v", items)
|
||||
}
|
||||
if wx.Connected {
|
||||
t.Error("weixin-ilink connected = true, want false while the token is expired")
|
||||
t.Error("weixin-ilink connected = true, want false while iLink rejects the session")
|
||||
}
|
||||
if !wx.NeedsRescan {
|
||||
t.Error("weixin-ilink needsRescan = false, want true (expired token must ask for a re-scan)")
|
||||
t.Error("weixin-ilink needsRescan = false, want true (a rejected session must ask for a re-scan)")
|
||||
}
|
||||
if wx.LastError != reason {
|
||||
t.Errorf("weixin-ilink lastError = %q, want %q", wx.LastError, reason)
|
||||
@@ -135,10 +135,10 @@ func TestAdminChannelsReportsNeedsRescan(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdminChannelOpenExpiredSessionReturns409 asserts the open endpoint answers
|
||||
// honestly for a session a reconnect cannot fix: 409 session_expired with the
|
||||
// TestAdminChannelOpenRejectedSessionReturns409 asserts the open endpoint answers
|
||||
// honestly for a session a reconnect cannot fix: 409 session_rejected with the
|
||||
// re-scan instruction, never a 200 that would make the panel show "connected".
|
||||
func TestAdminChannelOpenExpiredSessionReturns409(t *testing.T) {
|
||||
func TestAdminChannelOpenRejectedSessionReturns409(t *testing.T) {
|
||||
t.Parallel()
|
||||
caps := capreg.New()
|
||||
if err := caps.Register(capreg.Capability{
|
||||
@@ -146,8 +146,8 @@ func TestAdminChannelOpenExpiredSessionReturns409(t *testing.T) {
|
||||
Kind: capreg.KindChannel,
|
||||
Value: &rescanStubAdapter{
|
||||
name: "weixin-ilink", rescan: true,
|
||||
reason: "iLink bot token 已过期",
|
||||
connectErr: weixin_ilink.ErrSessionExpired,
|
||||
reason: "iLink 返回 errcode=-14(本次会话已被 iLink 拒绝)",
|
||||
connectErr: weixin_ilink.ErrSessionRejected,
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("register weixin-ilink: %v", err)
|
||||
@@ -167,8 +167,8 @@ func TestAdminChannelOpenExpiredSessionReturns409(t *testing.T) {
|
||||
if err := json.Unmarshal(decodeBody(t, body)["error"], &eobj); err != nil {
|
||||
t.Fatalf("decode error envelope: %v (body=%s)", err, body)
|
||||
}
|
||||
if eobj.Code != "session_expired" {
|
||||
t.Fatalf("error code = %q, want session_expired", eobj.Code)
|
||||
if eobj.Code != "session_rejected" {
|
||||
t.Fatalf("error code = %q, want session_rejected", eobj.Code)
|
||||
}
|
||||
if !strings.Contains(eobj.Message, "扫码") {
|
||||
t.Fatalf("error message = %q, want the re-scan instruction", eobj.Message)
|
||||
@@ -198,7 +198,7 @@ func TestAdminChannelsRescanFromRealIlinkAdapter(t *testing.T) {
|
||||
t.Fatalf("new weixin adapter: %v", err)
|
||||
}
|
||||
if err := ad.Connect(context.Background()); err != nil {
|
||||
t.Fatalf("connect before expiry: %v", err)
|
||||
t.Fatalf("connect before rejection: %v", err)
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
@@ -231,10 +231,10 @@ func TestAdminChannelsRescanFromRealIlinkAdapter(t *testing.T) {
|
||||
t.Fatalf("channels = %+v, want one weixin-ilink row", items)
|
||||
}
|
||||
if items[0].Connected {
|
||||
t.Error("connected = true although iLink rejected the token")
|
||||
t.Error("connected = true although iLink rejected the session")
|
||||
}
|
||||
if !items[0].NeedsRescan {
|
||||
t.Error("needsRescan = false, want true for an expired iLink session")
|
||||
t.Error("needsRescan = false, want true for a rejected iLink session")
|
||||
}
|
||||
if items[0].LastError == "" {
|
||||
t.Error("lastError is empty, want the re-scan reason")
|
||||
|
||||
@@ -240,9 +240,9 @@ func (s *Server) adminChannelOpen(w http.ResponseWriter, r *http.Request) {
|
||||
// the weixin-iLink bot token was rejected by iLink (errcode=-14), so the
|
||||
// owner needs a fresh scan-code login. Answer 409 with that instruction
|
||||
// instead of pretending the channel opened (the panel marks it connected).
|
||||
if errors.Is(err, weixin_ilink.ErrSessionExpired) {
|
||||
writeError(w, apperr.Wrap(apperr.ErrConflict, "session_expired",
|
||||
"该渠道会话已过期(需要重新扫码登录):请用「扫码登录」重新绑定后再连接"))
|
||||
if errors.Is(err, weixin_ilink.ErrSessionRejected) {
|
||||
writeError(w, apperr.Wrap(apperr.ErrConflict, "session_rejected",
|
||||
"该渠道会话已被 iLink 拒绝(errcode=-14):请用「扫码登录」重新绑定后再连接"))
|
||||
return
|
||||
}
|
||||
// weixin-iLink with no token yet must not hang nor read as a credential
|
||||
|
||||
Reference in new issue
Block a user