主人 2026-10-03 拍板「注释一律中文」;gateway 至此整包注释中文化完成(本会话共 3 批: 已推库 16 个文件 + 本批剩余文件)。 校验:机械校验(非注释改动行)= 空;剥注释后代码文本逐行比对全等(不一致文件数 0); 全树 go test ./... 58 包 ok / 0 FAIL;交叉编译绿。
90 lines
3.1 KiB
Go
90 lines
3.1 KiB
Go
package gateway
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"time"
|
||
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/auth"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
|
||
)
|
||
|
||
// wsProactiveMessage 是自主主动推送的 WS 事件类型
|
||
// (design/12 §3):Cyrene 在没有近期用户输入时决定发送的消息。
|
||
const wsProactiveMessage = "proactive_message"
|
||
|
||
// proactivePayload 是主动消息消毒后的线上结构。它只
|
||
// 携带客户端渲染气泡所需的字段(content、platform、channel、
|
||
// 发送者标签);它刻意省略 RawData / 媒体字节 / token,使
|
||
// 主动推送绝不可能把凭据或载荷泄露进前端。
|
||
type proactivePayload struct {
|
||
Platform string `json:"platform,omitempty"`
|
||
ChannelID string `json:"channelId,omitempty"`
|
||
ChannelType string `json:"channelType,omitempty"`
|
||
Content string `json:"content"`
|
||
ContentType string `json:"contentType,omitempty"`
|
||
Sender string `json:"sender,omitempty"`
|
||
Timestamp time.Time `json:"timestamp,omitempty"`
|
||
MessageID string `json:"messageId,omitempty"`
|
||
}
|
||
|
||
// SendProactive 把 msg 投递给每个存活的 owner/admin 连接,并返回
|
||
// 有多少个接受了它。计数为零意味着没有 owner/admin 客户端在线,
|
||
// 因此调用方回退到 IM bridge。只有 owner/admin 连接会被作为
|
||
// 目标(红线:主动推送仅 owner);viewer 或未认证连接
|
||
// 绝不被触达。它绝不阻塞:发送缓冲满时对该
|
||
// 慢客户端丢弃该帧,而不是卡住后台思考推送。
|
||
func (h *hub) SendProactive(ctx context.Context, msg adapter.UnifiedMessage) (int, error) {
|
||
if err := ctx.Err(); err != nil {
|
||
return 0, err
|
||
}
|
||
b, err := json.Marshal(wsEvent{Type: wsProactiveMessage, Data: proactivePayloadFrom(msg)})
|
||
if err != nil {
|
||
return 0, fmt.Errorf("gateway: marshal proactive: %w", err)
|
||
}
|
||
h.mu.RLock()
|
||
defer h.mu.RUnlock()
|
||
n := 0
|
||
for c := range h.conns {
|
||
if !ownerAdminClaims(c.claims) {
|
||
continue
|
||
}
|
||
select {
|
||
case c.send <- b:
|
||
n++
|
||
default:
|
||
}
|
||
}
|
||
return n, nil
|
||
}
|
||
|
||
// Server.SendProactive 通过委托给 hub 满足后台思考的 ActiveClient
|
||
// 接缝,因此组合根能把网关自身交给汇聚点设置器。
|
||
func (s *Server) SendProactive(ctx context.Context, msg adapter.UnifiedMessage) (int, error) {
|
||
return s.hub.SendProactive(ctx, msg)
|
||
}
|
||
|
||
// proactivePayloadFrom 把 UnifiedMessage 投影为消毒后的线上载荷。
|
||
func proactivePayloadFrom(msg adapter.UnifiedMessage) proactivePayload {
|
||
return proactivePayload{
|
||
Platform: msg.Platform,
|
||
ChannelID: msg.ChannelID,
|
||
ChannelType: string(msg.ChannelType),
|
||
Content: msg.Content,
|
||
ContentType: string(msg.ContentType),
|
||
Sender: msg.Sender.DisplayName,
|
||
Timestamp: msg.Timestamp,
|
||
MessageID: msg.MessageID,
|
||
}
|
||
}
|
||
|
||
// ownerAdminClaims 报告 claims 是否授权一次主动推送(owner 或
|
||
// admin)。nil claims(未认证 / 裸测试连接)绝不被作为目标。
|
||
func ownerAdminClaims(claims *auth.Claims) bool {
|
||
if claims == nil {
|
||
return false
|
||
}
|
||
return claims.Role == auth.RoleOwner || claims.Role == auth.RoleAdmin
|
||
}
|