package backthink // 生产组合根在 backthink.New *之后* 才构建 gateway hub 与 channel // bridge(hub 需要 loop,bridge 需要 loop 与主人档案, // 两者都在更晚时才组装),因此构造时拿不到 ActiveClient 与 // IMAdapter 槽。这些设置器在 gateway / bridge 存在后注入它们, // 并重建推送器,同时保留已解出的 // 频率门,使之后的推送路由正确。 // SetActiveSink 接入生产用的活跃 Web/原生客户端槽(gateway // hub)并重建推送器。请在 gateway 存在之后、Start 之前调用。 func (b *Backthink) SetActiveSink(active ActiveClient) { b.mu.Lock() defer b.mu.Unlock() b.deps.Active = active b.rebuildPusherLocked() } // SetIMSink 接入生产用的 IM 桥槽(channel bridge)并重建 // 推送器。请在 channel bridge 存在之后、Start 之前调用。 func (b *Backthink) SetIMSink(im IMAdapter) { b.mu.Lock() defer b.mu.Unlock() b.deps.IM = im b.rebuildPusherLocked() } // SetAuditSink 接入审计出口(网关,design/12 §N C.5)。它与上面两个 setter 同属 // **晚绑定**:网关比 backthink 晚组装,构造时拿不到。它不重建推送器——审计不在推送 // 路径上,换一个出口(或注入 nil)都不影响投递。 func (b *Backthink) SetAuditSink(a Auditor) { b.mu.Lock() defer b.mu.Unlock() b.deps.Audit = a } // currentAuditor 在生命周期锁下返回当前审计出口,因此在途的一轮自排拿到的是 // 一致快照,而不是「一半旧一半新」的两个出口。 func (b *Backthink) currentAuditor() Auditor { b.mu.Lock() defer b.mu.Unlock() return b.deps.Audit } // rebuildPusherLocked 用当前槽位、频率门与推送出口替换 // 推送器。调用方必须持有 b.mu。 // // 审计出口挂的是 `b.auditEvent` 这个**读取当前出口**的回调,而不是一次性快照: // SetAuditSink 是晚绑定的(网关比 backthink 晚组装),持快照会让稍后接上的网关 // 永远收不到推送审计 —— 与「未接线则静默跳过」同一条诚实口径。 func (b *Backthink) rebuildPusherLocked() { b.pusher = newPusher(b.deps.Active, b.deps.IM, b.gate, b.log). withExporter(b.deps.Exporter). withAuditor(b.auditEvent) } // rebuildPusher 是不持锁版本的 rebuildPusherLocked,供 New 在结构体刚构造好、 // 尚无并发访问者时使用。 func (b *Backthink) rebuildPusher() { b.mu.Lock() defer b.mu.Unlock() b.rebuildPusherLocked() } // currentPusher 在生命周期锁下返回当前推送器,因此一次槽位 // 换入绝不会与即将推送的并发思考轮发生竞态。 func (b *Backthink) currentPusher() Pusher { b.mu.Lock() defer b.mu.Unlock() return b.pusher }