fix(channel): 热重载把新适配器写回能力注册表
面板保存渠道配置后 Bridge.Reload 只重建了桥接器自己的适配器表,能力注册表里的条目仍指向保存前的旧实例;而管理面的渠道列表、每账号连接状态、open/close 的目标解析全部读注册表 —— 于是卡片永远「未连接」,点「连接」驱动的是旧实例(按旧 URL 拨号)。 生产日志实证:保存后 bridge 侧新实例连上 ws://localhost:3001 且心跳正常,而点连接时被驱动的适配器拨的是旧地址 ws://localhost:10301。 改法:注册表新增 upsert 语义的 ReplaceChannel(覆盖 Value、保留运行期 Policy;Register 遇重名会 ErrDuplicate,热重载必然同名);桥接器经窄端口在适配器上线后同步,账号被移除时删除条目;组合根注入 a.caps 包装。同步失败只记日志、不中断 Reload;未接线时保持旧行为并只 WARN 一次。 验收:go build ./... + go vet 全绿;registry/channelbridge/gateway 三包 test 全绿;主会话亲自变异(摘掉 Policy 沿用 ⇒ TestReplaceChannelSwapsValueAndKeepsPolicy 红)。linux/darwin 交叉编译通过。
This commit is contained in:
1 parent
23b6e3fe1e
commit
d81635672e
6 files changed
+560
-6
No files matched your search
@@ -17,6 +17,7 @@ import (
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/services/channelbridge"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/user"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/pkg/plugins/sdk"
|
||||
)
|
||||
|
||||
// ownerRegistrar 实现 gateway.OwnerRegistrar:它把一个渠道账号 id 持久化进主人
|
||||
@@ -57,6 +58,30 @@ func (r *ownerRegistrar) RegisterChannelOwner(_ context.Context, platform, accou
|
||||
// 加载失败则为 nil,gateway 会退回「真实落盘 + 提示需重启」,绝不假装热生效。
|
||||
func (r *ownerRegistrar) LiveOwnerProfile() *user.OwnerProfile { return r.owner }
|
||||
|
||||
// channelCaps 把能力注册表适配成 channelbridge.ChannelRegistry(热重载同步注册表的
|
||||
// 窄端口)。替换时写入的形状与 wireChannels 启动注册**逐字相同**
|
||||
// (Name/Kind/Description/Value):Description 取适配器名、Value 取适配器本身 ——
|
||||
// adminListChannels 把 Value 断言成 sdk.Channel、面板按注册名精确匹配,
|
||||
// 形状一旦不一致,渠道就会从面板上消失。
|
||||
type channelCaps struct{ caps *registry.Registry }
|
||||
|
||||
func (c channelCaps) ReplaceChannel(ch sdk.Channel) error {
|
||||
if ch == nil {
|
||||
return fmt.Errorf("wire channel registry: %w", registry.ErrInvalidArgument)
|
||||
}
|
||||
if err := c.caps.ReplaceChannel(ch); err != nil {
|
||||
return fmt.Errorf("wire channel registry %q: %w", ch.Name(), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c channelCaps) RemoveChannel(name string) error {
|
||||
if err := c.caps.Remove(name); err != nil {
|
||||
return fmt.Errorf("wire channel registry %q: %w", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// wireChannels 依环境配置构造四个 IM 渠道适配器,把它们注册进能力注册表(管理
|
||||
// API 据此枚举),并接进渠道 bridge——让已配置的适配器连上,把入站消息按
|
||||
// session→actor→loop→reply 路由。缺凭据的适配器保持未连接(只记日志,绝不作
|
||||
@@ -122,6 +147,10 @@ func (a *app) wireChannels() error {
|
||||
// EngageFor 留空(P2a);按群覆盖随 P2c 到来。
|
||||
// Factory 让面板保存时热替换某平台的适配器而无需重启(design/04 §3.1)。
|
||||
Factory: factory,
|
||||
// Channels 让热重载把新适配器写回能力注册表:管理面板(渠道卡片、账号在线
|
||||
// 状态、open/close 的目标解析)读的就是注册表,不同步它就只能看到旧实例,
|
||||
// 于是卡片永远「未连接」、点「连接」驱动的是旧 URL 的那把。
|
||||
Channels: channelCaps{caps: a.caps},
|
||||
// ChannelPersona 从工厂构建所用的同一份配置里解析账号的渠道级角色
|
||||
// (design/22 §2 方案 b);bridge 按已连接的适配器缓存它,并在每次重载时刷新。
|
||||
ChannelPersona: factory.Persona,
|
||||
|
||||
Reference in new issue
Block a user