Gateway架构:多平台消息接入的统一Harness设计——打造企业级AI Agent消息网关
关键词:Gateway网关、多平台接入、消息路由、Harness架构、企业微信、Telegram机器人、Discord、Slack、飞书 摘要:在AI Agent系统中,如何优雅地处理来自Telegram、Discord、Slack、飞书、企业微信等不同平台的消息,并统一接入Harness引擎?本文提出“Gateway网关层”设计模式,全面解析消息路由、身份识别、格式转换、协议适配等核心职责。通过Go语言实战,演示多平台SDK集成、连接池管理、限流重试、状态同步等关键技术,最终构建一个生产级的统一消息网关。全文包含大量Mermaid架构图与代码片段,助力读者构建跨渠道智能助手。
📑 目录
- 3.1 消息路由
- 3.2 身份识别与统一用户映射
- 3.3 格式转换
- 3.4 协议适配
- 4.1 跨渠道身份一致性
- 4.2 限流与重试策略
- 4.3 连接池管理
- 5.1 Telegram Bot接入
- 5.2 Discord Bot接入
- 5.3 Slack Bot接入
- 5.4 飞书机器人接入
- 5.5 企业微信接入
- 6.1 消息处理流程:解析→路由→执行→回复
- 6.2 异步与同步模式选择
- 7.1 消息已读回执同步
- 7.2 对话状态跨渠道漂移
1. 为什么需要Gateway架构?
现代AI Agent系统往往需要同时服务多个消息平台:员工用企业微信、客户用Telegram、社区用户用Discord、合作伙伴用Slack……如果每个平台单独对接Harness引擎,会造成以下问题:
- 代码重复:每个平台都要实现消息接收、解析、回复发送、错误重试等逻辑。
- 逻辑耦合:业务逻辑(意图识别、工具调用)与平台特性(消息格式、速率限制)混在一起,难以维护。
- 状态孤岛:用户在不同平台上的对话历史无法共享,体验割裂。
- 扩展困难:新增一个平台需要改动Harness核心代码。
Gateway(网关)层作为前置适配层,将所有平台的消息统一转换成内部标准格式,再交给Harness引擎处理;引擎的回复再通过网关分发回原平台。这种架构类似于微服务中的API网关模式。
下面我们通过一张Mermaid图展示Gateway在整个系统中的位置。
2. Gateway在Harness系统中的位置
#mermaid-svg-DfM7aEmmXSmOvGSK{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-DfM7aEmmXSmOvGSK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-DfM7aEmmXSmOvGSK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-DfM7aEmmXSmOvGSK .error-icon{fill:#552222;}#mermaid-svg-DfM7aEmmXSmOvGSK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-DfM7aEmmXSmOvGSK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-DfM7aEmmXSmOvGSK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-DfM7aEmmXSmOvGSK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-DfM7aEmmXSmOvGSK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-DfM7aEmmXSmOvGSK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-DfM7aEmmXSmOvGSK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-DfM7aEmmXSmOvGSK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-DfM7aEmmXSmOvGSK .marker.cross{stroke:#333333;}#mermaid-svg-DfM7aEmmXSmOvGSK svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-DfM7aEmmXSmOvGSK p{margin:0;}#mermaid-svg-DfM7aEmmXSmOvGSK .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-DfM7aEmmXSmOvGSK .cluster-label text{fill:#333;}#mermaid-svg-DfM7aEmmXSmOvGSK .cluster-label span{color:#333;}#mermaid-svg-DfM7aEmmXSmOvGSK .cluster-label span p{background-color:transparent;}#mermaid-svg-DfM7aEmmXSmOvGSK .label text,#mermaid-svg-DfM7aEmmXSmOvGSK span{fill:#333;color:#333;}#mermaid-svg-DfM7aEmmXSmOvGSK .node rect,#mermaid-svg-DfM7aEmmXSmOvGSK .node circle,#mermaid-svg-DfM7aEmmXSmOvGSK .node ellipse,#mermaid-svg-DfM7aEmmXSmOvGSK .node polygon,#mermaid-svg-DfM7aEmmXSmOvGSK .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-DfM7aEmmXSmOvGSK .rough-node .label text,#mermaid-svg-DfM7aEmmXSmOvGSK .node .label text,#mermaid-svg-DfM7aEmmXSmOvGSK .image-shape .label,#mermaid-svg-DfM7aEmmXSmOvGSK .icon-shape .label{text-anchor:middle;}#mermaid-svg-DfM7aEmmXSmOvGSK .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-DfM7aEmmXSmOvGSK .rough-node .label,#mermaid-svg-DfM7aEmmXSmOvGSK .node .label,#mermaid-svg-DfM7aEmmXSmOvGSK .image-shape .label,#mermaid-svg-DfM7aEmmXSmOvGSK .icon-shape .label{text-align:center;}#mermaid-svg-DfM7aEmmXSmOvGSK .node.clickable{cursor:pointer;}#mermaid-svg-DfM7aEmmXSmOvGSK .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-DfM7aEmmXSmOvGSK .arrowheadPath{fill:#333333;}#mermaid-svg-DfM7aEmmXSmOvGSK .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-DfM7aEmmXSmOvGSK .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-DfM7aEmmXSmOvGSK .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DfM7aEmmXSmOvGSK .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-DfM7aEmmXSmOvGSK .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DfM7aEmmXSmOvGSK .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-DfM7aEmmXSmOvGSK .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-DfM7aEmmXSmOvGSK .cluster text{fill:#333;}#mermaid-svg-DfM7aEmmXSmOvGSK .cluster span{color:#333;}#mermaid-svg-DfM7aEmmXSmOvGSK div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-DfM7aEmmXSmOvGSK .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-DfM7aEmmXSmOvGSK rect.text{fill:none;stroke-width:0;}#mermaid-svg-DfM7aEmmXSmOvGSK .icon-shape,#mermaid-svg-DfM7aEmmXSmOvGSK .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DfM7aEmmXSmOvGSK .icon-shape p,#mermaid-svg-DfM7aEmmXSmOvGSK .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-DfM7aEmmXSmOvGSK .icon-shape .label rect,#mermaid-svg-DfM7aEmmXSmOvGSK .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DfM7aEmmXSmOvGSK .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-DfM7aEmmXSmOvGSK .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-DfM7aEmmXSmOvGSK :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
存储层
核心层
Gateway 网关层
外部消息平台
Telegram
Discord
Slack
飞书
企业微信
多平台适配器
消息路由器
格式转换器
身份识别
Harness 引擎ReAct循环/工具/LLM
Redis会话/限流
数据库用户映射
各模块职责:
- 适配器:负责监听各平台的消息事件,调用平台SDK收发消息。
- 格式转换器:将平台特有的消息结构(Telegram的Message、企业微信的TextMessage)统一为内部GatewayMessage。
- 身份识别:根据平台类型+平台用户ID,映射到内部统一的UserID(例如telegram:123456 -> user_10086)。
- 消息路由器:根据用户ID、会话ID或意图,将消息分发到对应的Harness实例或会话。
3. Gateway核心职责详解
3.1 消息路由
消息路由决定了一条消息应该交给哪个Harness处理。常见策略:
- 单例模式:所有消息路由到同一个Harness引擎(适合个人助理)。
- 会话粘滞:同一会话(如Telegram的chat_id)的消息始终发往同一个Harness实例。
- 意图分流:技术问题发往技术助手,销售问题发往销售助手。
路由表通常存储在Redis中:
type Router struct {
backend map[string]HarnessClient // sessionID -> Harness地址
}
func (r *Router) Route(sessionID string) HarnessClient {
if client, ok := r.backend[sessionID]; ok {
return client
}
// 分配新实例
client := r.selectLeastLoaded()
r.backend[sessionID] = client
return client
}
3.2 身份识别与统一用户映射
不同平台用户标识不同:Telegram用user.id,Discord用user.id(但guild不同),企业微信用UserID。Gateway需要维护一个映射表:
type IdentityResolver struct {
db *sql.DB
}
func (ir *IdentityResolver) Resolve(platform string, platformUserID string) (internalUserID string, err error) {
row := ir.db.QueryRow("SELECT internal_user_id FROM user_mapping WHERE platform=? AND platform_user_id=?", platform, platformUserID)
if err == sql.ErrNoRows {
// 创建新用户
internalUserID = generateUUID()
ir.db.Exec("INSERT INTO user_mapping VALUES (?,?,?)", internalUserID, platform, platformUserID)
return internalUserID, nil
}
return
}
3.3 格式转换
内部统一消息格式示例:
type GatewayMessage struct {
ID string // 全局唯一ID
Platform string // telegram, discord, slack, feishu, wecom
ChannelID string // 会话ID(群或私聊)
Sender User // 发送者信息
Content string // 文本内容
RawData interface{} // 原始消息(用于扩展)
Attachments []Attachment // 图片、文件等
Timestamp time.Time
}
type User struct {
InternalID string // 内部统一ID
PlatformID string // 平台原始ID
DisplayName string
}
转换器实现:
func ConvertTelegram(update *tgbotapi.Update) *GatewayMessage {
msg := update.Message
return &GatewayMessage{
ID: fmt.Sprintf("tg_%d", msg.MessageID),
Platform: "telegram",
ChannelID: strconv.FormatInt(msg.Chat.ID, 10),
Sender: User{
PlatformID: strconv.Itoa(msg.From.ID),
DisplayName: msg.From.FirstName + " " + msg.From.LastName,
},
Content: msg.Text,
RawData: msg,
Timestamp: time.Unix(int64(msg.Date), 0),
}
}
3.4 协议适配
不同平台的通信协议差异:
- Telegram:长轮询或Webhook(HTTP回调)。
- Discord:Gateway WebSocket(需要维持连接)。
- Slack:RTM API或Event Subscription(HTTP)。
- 飞书/企业微信:HTTP回调(需验证签名)。
Gateway适配器负责启动相应的监听器,并统一转换为内部事件流。
4. 多平台接入设计要点
4.1 跨渠道身份一致性
用户可能同时在Telegram和企业微信上与机器人对话。如何识别为同一用户?可以采用绑定码方式:用户在任一平台发送绑定指令,机器人返回一次性绑定码,用户在其他平台输入该码完成关联。关联信息存储后,后续所有消息都映射到同一internalUserID。
WX
机器人
Gateway
Telegram用户
WX
机器人
Gateway
Telegram用户
#mermaid-svg-uLccotOmzMXZ6BYv{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-uLccotOmzMXZ6BYv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-uLccotOmzMXZ6BYv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-uLccotOmzMXZ6BYv .error-icon{fill:#552222;}#mermaid-svg-uLccotOmzMXZ6BYv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-uLccotOmzMXZ6BYv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-uLccotOmzMXZ6BYv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-uLccotOmzMXZ6BYv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-uLccotOmzMXZ6BYv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-uLccotOmzMXZ6BYv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-uLccotOmzMXZ6BYv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-uLccotOmzMXZ6BYv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-uLccotOmzMXZ6BYv .marker.cross{stroke:#333333;}#mermaid-svg-uLccotOmzMXZ6BYv svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-uLccotOmzMXZ6BYv p{margin:0;}#mermaid-svg-uLccotOmzMXZ6BYv .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-uLccotOmzMXZ6BYv text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-uLccotOmzMXZ6BYv .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-uLccotOmzMXZ6BYv .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-uLccotOmzMXZ6BYv .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-uLccotOmzMXZ6BYv .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-uLccotOmzMXZ6BYv #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-uLccotOmzMXZ6BYv .sequenceNumber{fill:white;}#mermaid-svg-uLccotOmzMXZ6BYv #sequencenumber{fill:#333;}#mermaid-svg-uLccotOmzMXZ6BYv #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-uLccotOmzMXZ6BYv .messageText{fill:#333;stroke:none;}#mermaid-svg-uLccotOmzMXZ6BYv .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-uLccotOmzMXZ6BYv .labelText,#mermaid-svg-uLccotOmzMXZ6BYv .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-uLccotOmzMXZ6BYv .loopText,#mermaid-svg-uLccotOmzMXZ6BYv .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-uLccotOmzMXZ6BYv .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-uLccotOmzMXZ6BYv .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-uLccotOmzMXZ6BYv .noteText,#mermaid-svg-uLccotOmzMXZ6BYv .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-uLccotOmzMXZ6BYv .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-uLccotOmzMXZ6BYv .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-uLccotOmzMXZ6BYv .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-uLccotOmzMXZ6BYv .actorPopupMenu{position:absolute;}#mermaid-svg-uLccotOmzMXZ6BYv .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-uLccotOmzMXZ6BYv .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-uLccotOmzMXZ6BYv .actor-man circle,#mermaid-svg-uLccotOmzMXZ6BYv line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-uLccotOmzMXZ6BYv :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
/bind
生成绑定码 "ABC123"
请在微信输入 ABC123
ABC123
验证绑定码
关联两个平台的用户ID
绑定成功
4.2 限流与重试策略
每个平台都有API速率限制。Gateway应实现:
- 令牌桶/漏桶限制发送频率。
- 指数退避重试:当发送失败时,重试3次,间隔1s、2s、4s。
- 死信队列:超过重试次数的消息存入队列,人工介入。
type RateLimiter struct {
limiter *rate.Limiter // golang.org/x/time/rate
}
func (rl *RateLimiter) SendWithRetry(sendFunc func() error) error {
err := rl.limiter.Wait(context.Background())
if err != nil {
return err
}
for i := 0; i < 3; i++ {
err = sendFunc()
if err == nil {
return nil
}
time.Sleep(time.Duration(math.Pow(2, float64(i))) * time.Second)
}
return fmt.Errorf("max retries exceeded: %v", err)
}
4.3 连接池管理
对于WebSocket长连接(Discord、Slack RTM),需要管理连接生命周期、心跳、断线重连。建议每个平台一个连接池,按guild或channel复用连接。
type DiscordConnectionPool struct {
conns map[string]*discordgo.Session // key: guildID
mu sync.RWMutex
}
func (p *DiscordConnectionPool) Get(guildID string) *discordgo.Session {
p.mu.RLock()
defer p.mu.RUnlock()
return p.conns[guildID]
}
5. 平台集成实战
以下使用Go语言演示各平台接入代码片段(仅核心部分)。
5.1 Telegram Bot接入
使用go-telegram-bot-api库,Webhook模式。
func StartTelegramBot(token string, gw *Gateway) {
bot, err := tgbotapi.NewBotAPI(token)
if err != nil {
panic(err)
}
updateChan := bot.ListenForWebhook("/webhook/telegram")
go func() {
for update := range updateChan {
msg := ConvertTelegram(&update)
gw.OnMessage(msg)
}
}()
}
5.2 Discord Bot接入
使用discordgo库,Gateway Intents。
func StartDiscordBot(token string, gw *Gateway) {
dg, err := discordgo.New("Bot " + token)
if err != nil {
panic(err)
}
dg.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) {
if m.Author.ID == s.State.User.ID { return }
msg := ConvertDiscord(m)
gw.OnMessage(msg)
})
dg.Open()
}
5.3 Slack Bot接入
使用slack-go/slack,Socket Mode或Event API。
func StartSlackBot(token, appToken string, gw *Gateway) {
api := slack.New(token, slack.OptionDebug(true))
client := slack.NewSocketModeClient(slack.SocketModeOption{
Client: api,
AppToken: appToken,
})
client.AddHandler(socketmode.EventHandler(func(evt *socketmode.Event, client *socketmode.Client) {
if evt.Type == socketmode.EventTypeEventsAPI {
eventsAPI, _ := evt.Data.(slackevents.EventsAPIEvent)
if innerEvent := eventsAPI.InnerEvent; innerEvent.Data != nil {
switch msg := innerEvent.Data.(type) {
case *slackevents.MessageEvent:
gm := ConvertSlack(msg)
gw.OnMessage(gm)
}
}
}
}))
client.Run()
}
5.4 飞书机器人接入
飞书使用HTTP回调,需要验证签名。Gateway实现一个HTTP Handler。
func FeishuWebhookHandler(w http.ResponseWriter, r *http.Request) {
// 验证签名(略)
var req struct {
Header struct {
EventID string `json:"event_id"`
} `json:"header"`
Event struct {
Message struct {
MessageID string `json:"message_id"`
Content string `json:"content"` // JSON格式,需解析
SenderID string `json:"sender_id"`
CreateTime string `json:"create_time"`
} `json:"message"`
} `json:"event"`
}
json.NewDecoder(r.Body).Decode(&req)
// 解析Content中的text
var contentMap map[string]string
json.Unmarshal([]byte(req.Event.Message.Content), &contentMap)
gm := &GatewayMessage{
Platform: "feishu",
ChannelID: req.Event.Message.MessageID,
Content: contentMap["text"],
}
gw.OnMessage(gm)
}
5.5 企业微信接入
类似飞书,需要验证URL、解密消息。
func WecomWebhookHandler(w http.ResponseWriter, r *http.Request) {
// 使用企业微信提供的加解密库(略)
msg := ParseWecomMessage(r)
gm := &GatewayMessage{
Platform: "wecom",
ChannelID: msg.ChatID,
Sender: User{PlatformID: msg.FromUserID},
Content: msg.Text.Content,
}
gw.OnMessage(gm)
}
6. Gateway与Harness核心引擎的交互
6.1 消息处理流程:解析→路由→执行→回复
完整的数据流如下:
工具
大模型
Harness引擎
Router
Converter
Adapter
消息平台
工具
大模型
Harness引擎
Router
Converter
Adapter
消息平台
#mermaid-svg-t1xZNFLhhypLjesl{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-t1xZNFLhhypLjesl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-t1xZNFLhhypLjesl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-t1xZNFLhhypLjesl .error-icon{fill:#552222;}#mermaid-svg-t1xZNFLhhypLjesl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-t1xZNFLhhypLjesl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-t1xZNFLhhypLjesl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-t1xZNFLhhypLjesl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-t1xZNFLhhypLjesl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-t1xZNFLhhypLjesl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-t1xZNFLhhypLjesl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-t1xZNFLhhypLjesl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-t1xZNFLhhypLjesl .marker.cross{stroke:#333333;}#mermaid-svg-t1xZNFLhhypLjesl svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-t1xZNFLhhypLjesl p{margin:0;}#mermaid-svg-t1xZNFLhhypLjesl .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-t1xZNFLhhypLjesl text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-t1xZNFLhhypLjesl .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-t1xZNFLhhypLjesl .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-t1xZNFLhhypLjesl .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-t1xZNFLhhypLjesl .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-t1xZNFLhhypLjesl #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-t1xZNFLhhypLjesl .sequenceNumber{fill:white;}#mermaid-svg-t1xZNFLhhypLjesl #sequencenumber{fill:#333;}#mermaid-svg-t1xZNFLhhypLjesl #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-t1xZNFLhhypLjesl .messageText{fill:#333;stroke:none;}#mermaid-svg-t1xZNFLhhypLjesl .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-t1xZNFLhhypLjesl .labelText,#mermaid-svg-t1xZNFLhhypLjesl .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-t1xZNFLhhypLjesl .loopText,#mermaid-svg-t1xZNFLhhypLjesl .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-t1xZNFLhhypLjesl .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-t1xZNFLhhypLjesl .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-t1xZNFLhhypLjesl .noteText,#mermaid-svg-t1xZNFLhhypLjesl .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-t1xZNFLhhypLjesl .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-t1xZNFLhhypLjesl .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-t1xZNFLhhypLjesl .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-t1xZNFLhhypLjesl .actorPopupMenu{position:absolute;}#mermaid-svg-t1xZNFLhhypLjesl .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-t1xZNFLhhypLjesl .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-t1xZNFLhhypLjesl .actor-man circle,#mermaid-svg-t1xZNFLhhypLjesl line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-t1xZNFLhhypLjesl :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
alt
[有工具调用]
原始消息(Webhook/WebSocket)
原始对象
转换为GatewayMessage
统一消息
识别用户,查找会话
转发内部请求
调用模型推理
响应(含工具调用或最终答案)
执行工具
结果
再次调用(ReAct循环)
最终回复内容
回复+原始平台信息
发送消息(调用平台API)
6.2 异步与同步模式选择
- 同步模式:Gateway等待Harness返回结果后再回复平台(适用于Webhook有超时限制,如企业微信5秒)。对于长耗时任务,应先回复“正在处理中”,然后异步完成后再推送结果。
- 异步模式:Gateway收到消息后立即返回200,将消息放入队列,由Worker处理,通过平台API主动推送回复。
推荐实现:
func (gw *Gateway) OnMessage(msg *GatewayMessage) {
// 快速回复“正在思考”
gw.SendTyping(msg.ChannelID)
// 异步处理
go func() {
resp := gw.harness.Process(msg)
gw.SendMessage(msg.ChannelID, resp)
}()
}
7. 统一推送与多平台状态同步
7.1 消息已读回执同步
当用户在一个平台阅读了消息,Gateway可以同步到其他平台(如企业微信已读状态→Telegram)。实现需要平台支持(Telegram不支持已读回执,企业微信支持)。
7.2 对话状态跨渠道漂移
用户可能在手机上(Telegram)开始对话,然后切换到电脑(企业微信)继续。Gateway需要共享Harness的会话状态。解决方案:
- 将Harness会话存储在Redis中,以internalUserID为键。
- Gateway在不同平台的消息到达时,加载同一个会话。
type SessionStore struct {
redis *redis.Client
}
func (s *SessionStore) Get(userID string) *HarnessSession {
data, _ := s.redis.Get("session:" + userID).Bytes()
var sess HarnessSession
json.Unmarshal(data, &sess)
return &sess
}
状态同步流程:
#mermaid-svg-HUGWmG5lHhlOor6c{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-HUGWmG5lHhlOor6c .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HUGWmG5lHhlOor6c .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HUGWmG5lHhlOor6c .error-icon{fill:#552222;}#mermaid-svg-HUGWmG5lHhlOor6c .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HUGWmG5lHhlOor6c .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HUGWmG5lHhlOor6c .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HUGWmG5lHhlOor6c .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HUGWmG5lHhlOor6c .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HUGWmG5lHhlOor6c .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HUGWmG5lHhlOor6c .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HUGWmG5lHhlOor6c .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HUGWmG5lHhlOor6c .marker.cross{stroke:#333333;}#mermaid-svg-HUGWmG5lHhlOor6c svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HUGWmG5lHhlOor6c p{margin:0;}#mermaid-svg-HUGWmG5lHhlOor6c .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-HUGWmG5lHhlOor6c .cluster-label text{fill:#333;}#mermaid-svg-HUGWmG5lHhlOor6c .cluster-label span{color:#333;}#mermaid-svg-HUGWmG5lHhlOor6c .cluster-label span p{background-color:transparent;}#mermaid-svg-HUGWmG5lHhlOor6c .label text,#mermaid-svg-HUGWmG5lHhlOor6c span{fill:#333;color:#333;}#mermaid-svg-HUGWmG5lHhlOor6c .node rect,#mermaid-svg-HUGWmG5lHhlOor6c .node circle,#mermaid-svg-HUGWmG5lHhlOor6c .node ellipse,#mermaid-svg-HUGWmG5lHhlOor6c .node polygon,#mermaid-svg-HUGWmG5lHhlOor6c .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HUGWmG5lHhlOor6c .rough-node .label text,#mermaid-svg-HUGWmG5lHhlOor6c .node .label text,#mermaid-svg-HUGWmG5lHhlOor6c .image-shape .label,#mermaid-svg-HUGWmG5lHhlOor6c .icon-shape .label{text-anchor:middle;}#mermaid-svg-HUGWmG5lHhlOor6c .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-HUGWmG5lHhlOor6c .rough-node .label,#mermaid-svg-HUGWmG5lHhlOor6c .node .label,#mermaid-svg-HUGWmG5lHhlOor6c .image-shape .label,#mermaid-svg-HUGWmG5lHhlOor6c .icon-shape .label{text-align:center;}#mermaid-svg-HUGWmG5lHhlOor6c .node.clickable{cursor:pointer;}#mermaid-svg-HUGWmG5lHhlOor6c .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-HUGWmG5lHhlOor6c .arrowheadPath{fill:#333333;}#mermaid-svg-HUGWmG5lHhlOor6c .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HUGWmG5lHhlOor6c .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HUGWmG5lHhlOor6c .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HUGWmG5lHhlOor6c .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-HUGWmG5lHhlOor6c .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HUGWmG5lHhlOor6c .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-HUGWmG5lHhlOor6c .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-HUGWmG5lHhlOor6c .cluster text{fill:#333;}#mermaid-svg-HUGWmG5lHhlOor6c .cluster span{color:#333;}#mermaid-svg-HUGWmG5lHhlOor6c div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-HUGWmG5lHhlOor6c .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-HUGWmG5lHhlOor6c rect.text{fill:none;stroke-width:0;}#mermaid-svg-HUGWmG5lHhlOor6c .icon-shape,#mermaid-svg-HUGWmG5lHhlOor6c .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HUGWmG5lHhlOor6c .icon-shape p,#mermaid-svg-HUGWmG5lHhlOor6c .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-HUGWmG5lHhlOor6c .icon-shape .label rect,#mermaid-svg-HUGWmG5lHhlOor6c .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HUGWmG5lHhlOor6c .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-HUGWmG5lHhlOor6c .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-HUGWmG5lHhlOor6c :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
Telegram消息
GW
企业微信消息
Redis会话
Harness
8. 实战案例:构建一个跨平台客服机器人
需求:一个电商客服机器人,同时接入Telegram(海外客户)、企业微信(国内员工)、飞书(内部团队)。功能包括查订单、转人工、FAQ。
实现步骤:
效果演示(Mermaid流程图):
#mermaid-svg-Zl09TqGAM7yxzzPp{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Zl09TqGAM7yxzzPp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Zl09TqGAM7yxzzPp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Zl09TqGAM7yxzzPp .error-icon{fill:#552222;}#mermaid-svg-Zl09TqGAM7yxzzPp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Zl09TqGAM7yxzzPp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Zl09TqGAM7yxzzPp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Zl09TqGAM7yxzzPp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Zl09TqGAM7yxzzPp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Zl09TqGAM7yxzzPp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Zl09TqGAM7yxzzPp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Zl09TqGAM7yxzzPp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Zl09TqGAM7yxzzPp .marker.cross{stroke:#333333;}#mermaid-svg-Zl09TqGAM7yxzzPp svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Zl09TqGAM7yxzzPp p{margin:0;}#mermaid-svg-Zl09TqGAM7yxzzPp .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-Zl09TqGAM7yxzzPp .cluster-label text{fill:#333;}#mermaid-svg-Zl09TqGAM7yxzzPp .cluster-label span{color:#333;}#mermaid-svg-Zl09TqGAM7yxzzPp .cluster-label span p{background-color:transparent;}#mermaid-svg-Zl09TqGAM7yxzzPp .label text,#mermaid-svg-Zl09TqGAM7yxzzPp span{fill:#333;color:#333;}#mermaid-svg-Zl09TqGAM7yxzzPp .node rect,#mermaid-svg-Zl09TqGAM7yxzzPp .node circle,#mermaid-svg-Zl09TqGAM7yxzzPp .node ellipse,#mermaid-svg-Zl09TqGAM7yxzzPp .node polygon,#mermaid-svg-Zl09TqGAM7yxzzPp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Zl09TqGAM7yxzzPp .rough-node .label text,#mermaid-svg-Zl09TqGAM7yxzzPp .node .label text,#mermaid-svg-Zl09TqGAM7yxzzPp .image-shape .label,#mermaid-svg-Zl09TqGAM7yxzzPp .icon-shape .label{text-anchor:middle;}#mermaid-svg-Zl09TqGAM7yxzzPp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Zl09TqGAM7yxzzPp .rough-node .label,#mermaid-svg-Zl09TqGAM7yxzzPp .node .label,#mermaid-svg-Zl09TqGAM7yxzzPp .image-shape .label,#mermaid-svg-Zl09TqGAM7yxzzPp .icon-shape .label{text-align:center;}#mermaid-svg-Zl09TqGAM7yxzzPp .node.clickable{cursor:pointer;}#mermaid-svg-Zl09TqGAM7yxzzPp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Zl09TqGAM7yxzzPp .arrowheadPath{fill:#333333;}#mermaid-svg-Zl09TqGAM7yxzzPp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Zl09TqGAM7yxzzPp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Zl09TqGAM7yxzzPp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Zl09TqGAM7yxzzPp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Zl09TqGAM7yxzzPp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Zl09TqGAM7yxzzPp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Zl09TqGAM7yxzzPp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Zl09TqGAM7yxzzPp .cluster text{fill:#333;}#mermaid-svg-Zl09TqGAM7yxzzPp .cluster span{color:#333;}#mermaid-svg-Zl09TqGAM7yxzzPp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Zl09TqGAM7yxzzPp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Zl09TqGAM7yxzzPp rect.text{fill:none;stroke-width:0;}#mermaid-svg-Zl09TqGAM7yxzzPp .icon-shape,#mermaid-svg-Zl09TqGAM7yxzzPp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Zl09TqGAM7yxzzPp .icon-shape p,#mermaid-svg-Zl09TqGAM7yxzzPp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Zl09TqGAM7yxzzPp .icon-shape .label rect,#mermaid-svg-Zl09TqGAM7yxzzPp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Zl09TqGAM7yxzzPp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Zl09TqGAM7yxzzPp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Zl09TqGAM7yxzzPp :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
Telegram
内部消息
查订单
回复
Telegram
无法处理
企业微信
用户
GW
Harness
OrderAPI
人工坐席通道
Staff
9. 总结与最佳实践
本文详细阐述了Gateway架构在多平台消息接入中的价值与实现。核心要点总结如下:
| 消息路由 | 会话粘滞、Redis存储映射、一致性哈希 |
| 身份识别 | 独立用户映射表、绑定码机制 |
| 格式转换 | 内部标准GatewayMessage结构 |
| 协议适配 | 各平台SDK + Webhook/WebSocket管理器 |
| 限流重试 | 令牌桶 + 指数退避 + 死信队列 |
| 连接池管理 | 并发安全的map存储Session,心跳维护 |
| 状态同步 | Redis集中会话存储,跨平台加载 |
最佳实践建议:
- 统一错误处理:各平台错误码统一转换为内部错误码,便于监控。
- 可观测性:为每个消息注入trace_id,串联Gateway→Harness→LLM全链路日志。
- 配置化接入:使用YAML配置文件定义各平台的token、回调地址,支持热加载。
- 安全防护:验证平台回调签名,防止伪造消息。
下一步扩展:
- 支持语音消息(Telegram语音转文字)。
- 支持富媒体卡片(统一的消息卡片格式,各平台渲染适配)。
- 接入物联网设备(MQTT协议)作为新平台。
Gateway架构让AI Agent能够无感知地服务于所有用户,无论他们身在哪个聊天工具。希望本文能为你的跨平台机器人开发提供清晰的方向。
如果觉得本文对你有帮助,欢迎点赞、收藏、评论交流!你的支持是我持续分享的动力。
📚 参考资料
- Telegram Bot API
- Discord Developer Portal
- Slack API
- 飞书开放平台
- 企业微信开发者中心
- 《微服务架构设计模式》- Chris Richardson



