- 只翻注释/文档字符串:剥掉注释后代码行逐字节不变(机械校验只剩允许的行尾注释形态) - 术语对齐既有译法(owner→主人、persona→角色、engage→参与度判定、quiet hours→安静时段、backoff→退避) - 复核:go build 绿 + channelbridge 包测试绿;gofmt 干净
174 lines
4.6 KiB
Go
174 lines
4.6 KiB
Go
package channelbridge
|
||
|
||
import (
|
||
"context"
|
||
"net/http"
|
||
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
|
||
)
|
||
|
||
// 本文件承载渠道桥接器的适配器注册表与连接生命周期
|
||
// (design/08 §0):适配器经 Add 注册、由 Start 拨号
|
||
// (各自受 connectTimeout 约束)、由 Stop 拆除,并在不带
|
||
// 可用配置时被凭据闸门跳过。
|
||
|
||
// Add 按适配器的 Name 注册它。在 Start 之前调用是安全的;
|
||
// Start 之后再添加不影响已在运行的适配器。
|
||
func (b *Bridge) Add(a adapter.PlatformAdapter) {
|
||
if a == nil {
|
||
return
|
||
}
|
||
b.mu.Lock()
|
||
defer b.mu.Unlock()
|
||
b.adapters[a.Name()] = a
|
||
}
|
||
|
||
// WebhookHandlers 返回每个暴露 HTTP 回调的已注册适配器
|
||
// (如 *webhook.Adapter)的入站 HTTP 处理器,键为适配器配置中的
|
||
// 端点路径。gateway 挂载它们,把外部 webhook 回调
|
||
// 接入运行时。当没有任何适配器提供入站处理器时
|
||
// 返回 nil map。
|
||
func (b *Bridge) WebhookHandlers() map[string]http.Handler {
|
||
b.mu.RLock()
|
||
defer b.mu.RUnlock()
|
||
out := make(map[string]http.Handler)
|
||
for _, a := range b.adapters {
|
||
wh, ok := a.(webhookAdapter)
|
||
if !ok {
|
||
continue
|
||
}
|
||
path := wh.WebhookPath()
|
||
if path == "" {
|
||
// 适配器当前未暴露 webhook(例如 websocket 模式的
|
||
// KOOK);跳过它可避免把处理器挂在过期或冲突的路由上。
|
||
continue
|
||
}
|
||
h := wh.InboundHandler()
|
||
if h == nil {
|
||
continue
|
||
}
|
||
out[path] = h
|
||
}
|
||
if len(out) == 0 {
|
||
return nil
|
||
}
|
||
return out
|
||
}
|
||
|
||
// Start 连接每个已配置的适配器、装配入站处理器并
|
||
// 启动读取循环。没有凭据(或 Connect 失败)的适配器
|
||
// 只记日志并保持未连接;它们绝不会导致启动失败。Start 只
|
||
// 阻塞到连接完成(每个适配器受 connectTimeout 约束)便返回。它
|
||
// 是幂等的:第二次调用无副作用。
|
||
func (b *Bridge) Start() {
|
||
b.mu.Lock()
|
||
if b.closed || b.started {
|
||
b.mu.Unlock()
|
||
return
|
||
}
|
||
b.ctx, b.cancel = context.WithCancel(context.Background())
|
||
b.started = true
|
||
b.mu.Unlock()
|
||
|
||
for _, a := range b.snapshot() {
|
||
b.connectAndRun(a)
|
||
}
|
||
b.log.Info("channel bridge started", "connected", b.ConnectedCount(),
|
||
"registered", len(b.snapshot()))
|
||
}
|
||
|
||
// Stop 取消读取循环、断开所有适配器,并等待
|
||
// 处理中的消息完成。它是幂等的。
|
||
func (b *Bridge) Stop(ctx context.Context) {
|
||
b.mu.Lock()
|
||
if b.closed {
|
||
b.mu.Unlock()
|
||
return
|
||
}
|
||
b.closed = true
|
||
cancel := b.cancel
|
||
b.mu.Unlock()
|
||
|
||
cancel()
|
||
for name, a := range b.snapshot() {
|
||
if err := a.Disconnect(ctx); err != nil {
|
||
b.log.Warn("channel disconnect", "channel", name, "error", err)
|
||
}
|
||
}
|
||
b.wg.Wait()
|
||
b.log.Info("channel bridge stopped")
|
||
}
|
||
|
||
// IsConnected 报告指定名称的适配器当前是否已连接。
|
||
func (b *Bridge) IsConnected(name string) bool {
|
||
b.mu.RLock()
|
||
defer b.mu.RUnlock()
|
||
return b.connected[name]
|
||
}
|
||
|
||
// ConnectedCount 报告当前已连接的适配器数量。
|
||
func (b *Bridge) ConnectedCount() int {
|
||
b.mu.RLock()
|
||
defer b.mu.RUnlock()
|
||
n := 0
|
||
for _, ok := range b.connected {
|
||
if ok {
|
||
n++
|
||
}
|
||
}
|
||
return n
|
||
}
|
||
|
||
// snapshot 返回已注册适配器的副本,以便无锁遍历。
|
||
func (b *Bridge) snapshot() []adapter.PlatformAdapter {
|
||
b.mu.RLock()
|
||
defer b.mu.RUnlock()
|
||
out := make([]adapter.PlatformAdapter, 0, len(b.adapters))
|
||
for _, a := range b.adapters {
|
||
out = append(out, a)
|
||
}
|
||
return out
|
||
}
|
||
|
||
// configured 报告适配器是否带有足够配置可供尝试
|
||
// Connect。它检查适配器自身的 Config() 快照来看各平台所需的
|
||
// 凭据,因此空壳(无配置)适配器会被跳过而不是拨号。
|
||
// 正是这一点避免了 weixin-ilink 在缺少 bot token 时
|
||
// 落入会阻塞的扫码登录流程。
|
||
func (b *Bridge) configured(a adapter.PlatformAdapter) bool {
|
||
cfg := a.Config()
|
||
switch basePlatform(a.Name()) {
|
||
case "telegram", "weixin-ilink", "kook":
|
||
return cfgStr(cfg, "bot_token") != ""
|
||
case "qq-obv11":
|
||
if cfgStr(cfg, "mode") == "client" {
|
||
return cfgStr(cfg, "remoteURL") != ""
|
||
}
|
||
return cfgStr(cfg, "listenAddr") != ""
|
||
case "webhook":
|
||
// 仅入站:维持会话不需要外部凭据。
|
||
return true
|
||
default:
|
||
return true
|
||
}
|
||
}
|
||
|
||
// cfgStr 从适配器配置 map 中读取字符串字段,缺失时
|
||
// 返回 ""。
|
||
func cfgStr(m map[string]any, key string) string {
|
||
if m == nil {
|
||
return ""
|
||
}
|
||
if s, ok := m[key].(string); ok {
|
||
return s
|
||
}
|
||
return ""
|
||
}
|
||
|
||
// adapter 返回平台名对应的已注册适配器,没有则返回 nil。
|
||
func (b *Bridge) adapter(platform string) adapter.PlatformAdapter {
|
||
b.mu.RLock()
|
||
defer b.mu.RUnlock()
|
||
return b.adapters[platform]
|
||
}
|