package gateway import "reflect" // 本文件承载「适配器需要主人重新授权」这一可选能力, // 由 GET /api/v1/admin/channels 浮现(v0.39)。它位于渠道摘要 // 本身之旁 —— 而非其内 —— 因为摘要列表是 admin.go 中较大的 // handler 之一,而拆包辅助函数可复用。 // rescanAware 是渠道适配器在会话需要主人重新授权、而重连 // 无法提供该授权时所暴露的可选能力。 // *weixin_ilink.Adapter 实现了它(RescanRequired):iLink 应答 // errcode=-14 意味着机器人 token 已失效,主人必须扫新的二维码。 // 核心的 adapter.PlatformAdapter 契约被刻意不扩展,因此不需要 // 该能力的适配器不受任何改动。 // // 只有在适配器确实产出某值时才会报告它 —— 没有该能力的 // 平台保持字段省略,而不是送出一个猜测。 type rescanAware interface { // RescanRequired 报告是否需要一次新的扫码登录,以及原因 // (不需要时 reason 为 "")。 RescanRequired() (required bool, reason string) } // asRescanAware 报告已注册的渠道值是否暴露 rescan 能力。 // 注册时会把适配器包进 cmd/cyrene 的 namedAdapter —— 一个内嵌 // adapter.PlatformAdapter 的未导出结构体 —— 它只提升接口的 // 方法,因此具体适配器多出的能力只能通过拆开被内嵌字段 // 才能抵达。直接的适配器值(测试、单账号注册) // 按原样接受。 func asRescanAware(v any) (rescanAware, bool) { if ra, ok := v.(rescanAware); ok { return ra, true } inner, ok := unwrapNamedAdapter(v) if !ok { return nil, false } ra, ok := inner.(rescanAware) return ra, ok } // unwrapNamedAdapter 返回注册包装器内嵌在其导出 PlatformAdapter // 字段中的适配器。asKookAdmin 与 asWeixinLoginAdmin 为各自的 // 能力子集做同样的拆包,本可共用此辅助函数。 func unwrapNamedAdapter(v any) (any, bool) { rv := reflect.ValueOf(v) if !rv.IsValid() { return nil, false } if rv.Kind() == reflect.Ptr { rv = rv.Elem() } if rv.Kind() != reflect.Struct { return nil, false } f := rv.FieldByName("PlatformAdapter") if !f.IsValid() || !f.CanInterface() { return nil, false } return f.Interface(), true }