Files
Cyrene_v2/internal/services/gateway/proactive.go
T
AskaEth 074ffc4ebf
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
docs(gateway): gateway 剩余文件注释改中文(逐行 1:1,代码零字节改动)
主人 2026-10-03 拍板「注释一律中文」;gateway 至此整包注释中文化完成(本会话共 3 批:
已推库 16 个文件 + 本批剩余文件)。
校验:机械校验(非注释改动行)= 空;剥注释后代码文本逐行比对全等(不一致文件数 0);
全树 go test ./... 58 包 ok / 0 FAIL;交叉编译绿。
2026-10-03 22:28:25 +08:00

90 lines
3.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}