多平台消息处理架构¶
最后更新: 2026-08-04
背景¶
原框架的事件处理与 QQ 官方数据结构(dto.Payload)深度耦合,导致:
core/engine.Adapter接口直接依赖func(*dto.Payload)core/context.Context内部持有*dto.Payload和openapi.OpenAPI- 无法在不修改引擎核心的情况下接入其他平台
设计目标¶
- 平台无关:框架核心不直接引用任何平台 SDK 类型
- 渐进增强:通过
PlatformCapabilities运行时检测特性,优雅降级 - 可扩展:新平台只需实现标准接口,无需修改框架
层次架构¶
┌─────────────────────────────────────────┐
│ Bot / BotBuilder │ 用户 API 层
│ UsePlatformRegistry / WithPlatformRegistry │
├─────────────────────────────────────────┤
│ platform/ 抽象层 │ 接口定义层(本包)
│ PlatformAdapter / Event / Sender │
│ PlatformCapabilities / Registry │
├──────────────┬──────────────────────────┤
│ platform/qq │ platform/discord │ 平台实现层
│ platform/.. │ platform/telegram │
│ │ platform/wechat │
└──────────────┴──────────────────────────┘
核心接口¶
platform.Event¶
平台无关的事件抽象,所有平台事件均实现此接口:
type Event interface {
// EventIdentity 平台标识与事件分类
Platform() string // 平台标识,如 "qq"、"discord"
ID() string // 平台级唯一事件 ID(用于去重/追踪)
Kind() EventKind // 平台无关的事件类别(私聊、群聊、通知等)
// EventBody 事件正文
Content() string // 消息文本内容
Attachments() []Attachment
// 发送者和会话
Sender() UserInfo // 发送者信息
Chat() ChatInfo // 会话信息
Timestamp() time.Time // 事件时间戳
}
// 可选接口:访问平台原始数据
type RawEvent interface {
RawType() string // 平台原始事件类型字符串
RawPayload() any // 原始 payload(类型断言访问平台特定字段)
}
platform.ChatInfo¶
type ChatInfo struct {
ID string // 直接会话 ID(channel_id / group_id / user_id)
ParentID string // 父容器 ID(guild_id / 服务器 ID),私聊和普通群为空
Name string // 会话名称(可选,部分平台不提供)
IsGroup bool // 是否为群组/频道消息(false = 私聊)
IsDM bool // 是否为私信(DM)会话;与 IsGroup=true、ParentID 非空同时成立时为频道私信
Tokens []Token // 平台专属授权令牌(被动回复授权等)
}
platform.Adapter¶
平台适配器的核心接口:
type Adapter interface {
Platform() string // 平台标识(小写),如 "qq"、"discord"
Start(ctx context.Context, handler func(Event)) error // 启动事件循环,直到 ctx 取消
Stop(ctx context.Context) error // 停止适配器
Sender() Sender
Capabilities() Capabilities // 平台特性声明
}
platform.Capabilities¶
平台特性声明,用于 Handler 做渐进增强决策。支持两种检查方式:
caps.Has(platform.CapMarkdown | platform.CapEmbeds)(位掩码,推荐)或直接读布尔字段:
type Capabilities struct {
Markdown bool // 是否支持 Markdown 格式消息
Buttons bool // 是否支持交互按钮(内联键盘等)
MultiAttachment bool // 是否支持一条消息内多个附件
MessageEdit bool // 是否支持编辑已发送消息(MessageEditor 接口)
MessageDelete bool // 是否支持删除/撤回消息(MessageDeleter 接口)
Embeds bool // 是否支持富文本嵌入卡片
FileUpload bool // 是否支持二进制文件直传(非 URL)
GuildSupport bool // 是否有服务器/频道层级(ChatInfo.ParentID 有效)
Reactions bool // 是否支持表情回应
ThreadReply bool // 是否支持消息回复链/引用回复
TypingIndicator bool // 是否支持"正在输入"状态
MentionAll bool // 是否支持 @全体成员
VoiceChannel bool // 是否支持语音频道
Caption bool // 是否支持同一条消息内文本与附件同发(图文同发)
// ── 量化限制(0 = 无已知限制)────────────────────────
MaxTextLength int // 单条文本最大字符数(Discord=2000,Telegram=4096)
MaxAttachmentMB int // 单个附件最大大小 MB
MaxButtonsPerRow int // 每行最多按钮数
MaxButtonRows int // 最多按钮行数
MaxEmbedFields int // 单个 Embed 最多字段数
}
图文同发(CapCaption):
Caption声明平台支持"文本 + 附件同一条消息"。 Telegram(媒体 caption)、Discord(content+附件)、OneBot(CQ 码混排)、Satori(元素列表)支持; QQ 富媒体消息会丢弃文本,不声明此能力——插件据此选择"图文同发"或"图与文字分条发送"(如 pic/sauce 插件)。
platform.Button¶
交互按钮。支持三种形态:回调按钮、跳转链接按钮、指令按钮。
type Button struct {
ID string // 回调标识(Discord custom_id / Telegram callback_data)
Label string // 按钮显示文字
URL string // 跳转目标(Style == ButtonStyleLink 时有效)
Command string // 指令按钮:非空时点击后自动插入 "@bot <Command>" 由用户发送
Style ButtonStyle
Disabled bool // 是否置灰不可点击
Row int // 按钮行号(1-5),同 Row 值的按钮排同一行
Emoji string // 按钮前展示的 emoji(Discord 原生)
}
- 回调按钮(
ID非空):点击产生EventKindInteraction回调事件,Handler 可响应 - 指令按钮(
Command非空):点击后在输入框插入命令文本(如/help),由用户自行发送,不产生交互回调 ——规避了 QQ webhook 模式互动回调不可靠的问题(见 FAQ),适合"查看命令列表"类快捷入口 - 跳转按钮(
Style == ButtonStyleLink+URL):点击打开链接
platform.Sender¶
平台无关的消息发送接口。目标会话信息由框架自动从 Context 中的 ChatInfo 读取,
无需额外的 chatID 参数:
type Sender interface {
// 目标会话信息从 ctx 中的 ChatInfo 读取(由 Reply / WithChatInfo 注入)
Send(ctx context.Context, req SendRequest) (SendResult, error)
}
// 可选接口:支持消息编辑的平台实现此接口
type MessageEditor interface {
Edit(ctx context.Context, messageID string, msg OutboundMessage) error
}
// 可选接口:支持消息删除的平台实现此接口
type MessageDeleter interface {
Delete(ctx context.Context, messageID string) error
}
platform.OutboundMessage¶
平台无关的出站消息模型,支持多附件、富文本嵌入卡片和交互按钮:
type OutboundMessage struct {
Text string // 纯文本内容(最广泛兼容)
Markdown string // Markdown 内容(不支持时降级为 Text)
Attachments []Attachment // 附件列表(图片/音频/视频/文件,支持多附件)
Embeds []Embed // 富文本嵌入卡片(Discord 原生,其他平台降级)
Mentions []string // @用户 ID 列表
Buttons []Button // 交互按钮(Discord 组件/Telegram 内联键盘等)
ReplyToID string // 回复的目标消息 ID
Extra map[string]any // 平台特定扩展字段
}
EventKind 映射¶
| EventKind | QQ 平台事件类型 | 说明 |
|---|---|---|
EventKindPrivateMessage |
C2C_MESSAGE_CREATE |
私聊消息 |
EventKindGroupMessage |
GROUP_AT_MESSAGE_CREATE |
群 @机器人消息 |
EventKindGuildMessage |
AT_MESSAGE_CREATE / MESSAGE_CREATE |
频道消息 |
EventKindNotice |
GROUP_MSG_REJECT / GROUP_MSG_RECEIVE / C2C_MSG_REJECT / C2C_MSG_RECEIVE |
通知事件 |
EventKindSystem |
READY / RESUMED |
系统事件 |
EventKindMemberJoin |
GROUP_ADD_ROBOT / FRIEND_ADD |
成员加入/机器人被加入 |
EventKindMemberLeave |
GROUP_DEL_ROBOT / FRIEND_DEL |
成员离开/机器人被移除 |
EventKindInteraction |
— | 按钮回调/斜杠命令(待 QQ v2 适配) |
EventKindReaction |
— | 消息表情回应 |
EventKindMessageUpdate |
— | 消息被编辑 |
EventKindMessageDelete |
— | 消息被撤回/删除 |
使用方式¶
单平台(推荐入门用法)¶
adapter := qq.NewWebhookServerAdapter(":8080", botInfo)
bot, err := remilia.NewBotBuilder().
WithPlatformAdapter(adapter).
Build()
多平台注册表¶
registry := platform.NewRegistry()
registry.Register(qq.NewWebhookServerAdapter(":8080", botInfo))
// registry.Register(discord.NewAdapter(...)) // 未来接入其他平台
bot, err := remilia.NewBotBuilder().
WithPlatformRegistry(registry).
Build()
Handler 中的跨平台路由¶
// 匹配所有平台的私聊消息
engine.On(context.OnEventKind(platform.EventKindPrivateMessage),
context.OnCommand("/ping"),
).Handle(func(ctx *context.Context) error {
ctx.Reply(platform.TextMessage("pong"))
return nil
})
// 渐进增强:根据平台能力选择消息格式
engine.OnAny().Handle(func(ctx *context.Context) error {
event := ctx.GetPlatformEvent()
if event == nil {
return nil
}
// 通过 Registry 获取平台能力(或从 Context 中读取)
// 根据能力选择合适的消息格式
ctx.Reply(platform.TextMessage("hello"))
return nil
})
使用 ctx.Reply 发送消息¶
// 简单文本回复
ctx.Reply(platform.TextMessage("pong"))
// Markdown 回复(不支持的平台自动降级为纯文本)
ctx.Reply(platform.MarkdownMessage("# 标题\n正文"))
// 图片消息
ctx.Reply(platform.ImageMessage("https://example.com/img.png"))
// 富文本消息(多平台降级处理)
ctx.Reply(platform.TextMessage("").WithEmbeds(platform.Embed{
Title: "通知",
Description: "这是一条测试消息",
}))
// 使用 ChatInfo 直接发送(不依赖 ctx)
sendCtx := platform.WithChatInfo(context.Background(), platform.ChatInfo{
ID: "group-001",
IsGroup: true,
})
sender.Send(sendCtx, platform.TextMessage("公告"))
访问平台原始数据¶
engine.On(context.OnEventKind(platform.EventKindGuildMessage)).Handle(func(ctx *context.Context) error {
// 平台无关方式获取消息内容
content := ctx.GetMessageContent()
platform := ctx.GetEventPlatform() // "qq" / "discord" / ...
chat := ctx.GetPlatformEvent().Chat()
// 若需要 QQ 平台特定字段,通过 RawPayload 类型断言
if payload, ok := ctx.GetPlatformEvent().RawPayload().(*dto.Payload); ok {
// 访问 QQ 原始数据
_ = payload
}
return nil
})
实现新平台适配器¶
以实现 Telegram 适配器为例:
// platform/telegram/adapter.go
package telegram
import (
stdctx "context"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/KomeiDiSanXian/remilia/platform"
)
type Adapter struct {
bot *tgbotapi.BotAPI
sender *telegramSender
}
func (a *Adapter) Platform() string { return "telegram" }
func (a *Adapter) Sender() platform.Sender { return a.sender }
func (a *Adapter) Capabilities() platform.PlatformCapabilities {
return platform.PlatformCapabilities{
Markdown: true, Buttons: true, MultiAttachment: true,
MessageEdit: true, MessageDelete: true, FileUpload: true,
}
}
func (a *Adapter) StartPlatform(ctx stdctx.Context, handler func(platform.Event)) error {
u := tgbotapi.NewUpdate(0)
updates := a.bot.GetUpdatesChan(u)
for {
select {
case <-ctx.Done():
return nil
case update := <-updates:
handler(newTelegramEvent(update)) // 包装为 platform.Event
}
}
}
事件包装参考 platform/qq/event.go 的实现。
完整数据流¶
平台适配器事件循环
│
▼
adapter.Start(ctx, handler) // 每个平台适配器的事件循环
│ handler(platform.Event)
▼
Bot.handlePlatformEvent(event)
│ 获取该平台的 Sender(从 Registry 或 adapter.Sender())
│ engine.ProcessPlatformEvent(event, sender)
▼
context.NewContextFromEvent(event, sender)
│ 创建 *context.Context(ctx.ctx = Background())
▼
engine.processEventContext(ctx)
│ GetEventType() → string(event.Kind()) // 如 "PRIVATE_MESSAGE"
│ GetMessageContent() → event.Content()
▼
Matcher.Match(ctx) → Handler(ctx)
│ ctx.Reply(platform.OutboundMessage)
│ → platform.WithChatInfo(ctx.Context(), event.Chat())
│ → platform.Sender.Send(ctx, msg)
▼
context.ReleaseContextFromEvent(ctx) ← 归还对象池
目录结构¶
platform/
event.go # Event / UserInfo / ChatInfo / EventKind 接口定义
message.go # OutboundMessage / Attachment / Embed / Button 统一消息模型
adapter.go # Adapter / Sender / MessageEditor / MessageDeleter
# Capabilities / Registry / NoopSender
capabilities.go # Capabilities 位掩码(CapabilityFlag)与 Has()
registry.go # 多平台适配器注册表(并发启动/停止、致命错误通道)
platform_test.go # 接口与工具函数测试
qq/
adapter.go # QQ Adapter(webhook / websocket 两种模式)
webhook_server.go # WebhookServerAdapter(内置 HTTP 服务器 + 签名校验)
wsconn.go # websocket 适配器(v1.25.0 起支持)
event.go # qqEvent(包装 *dto.Payload 为 platform.Event)
sender.go # QQ Sender + QQCapabilities
event_test.go / webhook_server_test.go / wsconn_test.go
ark.go # 富媒体卡片(ark)支持
dlq/ # 死信队列支持
openapi/ # QQ OpenAPI 客户端
discord/ # 完整实现:adapter / event / sender / interactions / extra
telegram/ # 完整实现:adapter / client / event / sender / types
onebot/ # OneBot 实现:http_post / ws_reverse / message / event
satori/ # Satori 实现:webhook / ws / message_element / event
wechat/ # 完整实现:adapter / event / sender
milky/ # 完整实现:adapter / api / client / event / sender
terminal/ # 终端适配器(本地调试)
mock/ # 测试用 mock 适配器
core/context/
platform_event.go # NewContextFromEvent / Reply / GetEventKind 等
platform_event_test.go # 平台无关路径单元测试
core/engine/
process_platform.go # ProcessPlatformEvent / processEventContext(共享核心)
process_platform_test.go # 多平台路由集成测试
迁移完成状态¶
| 阶段 | 状态 | 说明 |
|---|---|---|
platform/ 抽象层 |
✅ 完成 | Event / Adapter / Sender / OutboundMessage / Capabilities(位掩码)/ Registry |
platform/qq 完整实现 |
✅ 完成 | WebhookServerAdapter / websocket 适配器 / qqSender / qqEvent 均实现 |
core/context 迁移 |
✅ 完成 | 完全切换到平台无关路径,旧 dto.Payload 字段已清除 |
core/engine 新入口 |
✅ 完成 | ProcessPlatformEvent 与旧 ProcessEvent 共用同一路由逻辑 |
| Bot 多平台注册表 | ✅ 完成 | UsePlatformRegistry / WithPlatformRegistry 完整实现 |
ctx.Reply() |
✅ 完成 | 平台无关发送,自动注入 ChatInfo,正确传播超时/取消信号 |
platform.EventKind 路由 |
✅ 完成 | OnEventKind() 支持跨平台规则匹配 |
platform/discord |
✅ 完成 | adapter / event / sender / interactions 完整实现 |
platform/telegram |
✅ 完成 | adapter / client / event / sender 完整实现 |
platform/onebot |
✅ 完成 | HTTP POST / WebSocket 反向连接实现 |
platform/satori |
✅ 完成 | Webhook / WebSocket / 消息元素实现 |
platform/wechat |
✅ 完成 | 完整实现 |
platform/milky |
✅ 完成 | 完整实现 |
platform/terminal |
✅ 完成 | 终端适配器(本地调试) |