package events // 本文件是**一条事件行到线上的投影**(design/26 §4.5;切片 E7)。 // // 为什么投影住在 events 包而不是网关:同一条事件有**两个**线上出口 —— // 管理 REST 的列表端点(`admin_channel_events.go`)与实时 WS 帧(`channel.event` // 主题,见 watcher.go / publish_store.go)。两处若各写一份结构,迟早会漂移成 // 「帧里有这个键、列表里没有」这类无从察觉的差异(正是 §9.2 反复强调的那类债)。 // 因此投影只写一次:**REST 的 DTO 是它的类型别名,实时帧的载荷也是它**。 // // 列与 `Event` 一一对应且不多不少:**没有**承载平台原文、媒体 URL、凭据或 OneBot // `flag` 的字段(§4.2 红线)。`summary` 是适配器或本包生成的固定文案,已净化, // 查不出原始文本。 import "time" // ChannelEventJSON 是一条 `channel_events` 行的线上投影(键名 camelCase,与契约 // v0.67 登记的 `events[]` 逐字一致)。 type ChannelEventJSON struct { // ID 是数据库自增主键。**可缺席**:实时帧里的事件行是**刚落库、id 还没回读**的 // 那一行,此时它被整个省略(而不是写一个 0)—— 0 会让客户端以为「所有实时帧 // 是同一行」。列表端点读的是已落盘的行,id 恒有值。 ID int64 `json:"id,omitempty"` Platform string `json:"platform"` AccountID string `json:"accountId"` Tier string `json:"tier"` Category string `json:"category"` PlatformType string `json:"platformType"` ChannelID string `json:"channelId"` ChannelType string `json:"channelType"` GroupID string `json:"groupId"` ActorID string `json:"actorId"` ActorRole string `json:"actorRole"` TargetID string `json:"targetId"` SelfRelated bool `json:"selfRelated"` MessageID string `json:"messageId"` Summary string `json:"summary"` CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` } // JSON 把一条事件行投影成线上结构。时间一律以 **UTC RFC3339** 回显(与 `llm/usage` // 同口径):本地时区偏移会让同一行的两个出口显示出不同的时刻。 // // id 为 0 时留空(见 ID 的注释):写侧不知道刚插入的行的 id,如实缺席比编一个强。 func (ev Event) JSON() ChannelEventJSON { return ChannelEventJSON{ ID: ev.ID, Platform: ev.Platform, AccountID: ev.AccountID, Tier: string(ev.Tier), Category: string(ev.Category), PlatformType: ev.PlatformType, ChannelID: ev.ChannelID, ChannelType: ev.ChannelType, GroupID: ev.GroupID, ActorID: ev.ActorID, ActorRole: ev.ActorRole, TargetID: ev.TargetID, SelfRelated: ev.SelfRelated, MessageID: ev.MessageID, Summary: ev.Summary, CreatedAt: ev.CreatedAt.UTC().Format(time.RFC3339), UpdatedAt: ev.UpdatedAt.UTC().Format(time.RFC3339), } }