渠道路由与会话解析

适用版本:ChengOS v0.1.0+ | 最后核对:2026-08-12 | 来源:chengflow-sdk/README.mdchengflow-sdk/src/sdk/session-manager.tscrates/cheng-core/src/domain/channel/mod.rscrates/cheng-core/src/ports/conversation_repo.rscrates/cheng-api/src/services/conversation_command.rs

一条入站消息必须回答两个彼此独立的问题:它属于哪一个会话? 以及 该运行哪一个工作流? 把二者混为一谈是网关建模中最常见的错误,ChengOS 刻意让它们分开。

三种身份

身份 回答什么 在哪里决定
channelId 消息来自哪个应用/哪条连接 渠道记录
workflowId 该运行什么 在渠道配置阶段绑定
session / external_chat_id 这属于哪个会话 每个外部聊天各一个

channelId 不是工作流标识。 它说明是哪个应用发来的消息、这个会话归哪条连接所有,网关用它把结果路由回正确的应用。工作流由一次显式绑定选定——渠道记录上的 bound_workflow_id——绝不从 channelId 推断。

这条分离是踩过坑才学到的:在网关重构之前,channelId 被直接映射成工作流,而浏览器前端还复用旧式适配器密钥去调 /channel/*。三件事——应用来源标识、工作流选择、适配器级鉴权——纠缠在同一个值上。现在它们被分开建模。

渠道记录

Channel
  ├── channel_id          ← 外部标识,工作区内唯一,
  │                         同时是 Webhook 的 URL 路径段
  ├── app_type            ← 路由键:原始字符串,绝不是枚举
  ├── bound_workflow_id   ← 消息到达时运行什么
  ├── connection_config   ← 对框架不透明
  ├── connection_state
  ├── setup_data
  └── enabled

app_type 选适配器,bound_workflow_id 选工作流。两者都是显式字段,谁也不从对方猜出来。

会话解析

入站消息通过一个三元组映射到会话:

(workspace_id, app_id, external_chat_id)  ──►  Conversation

find_or_create_by_channel幂等的:用同一个三元组反复调用会返回同一个会话。正因如此,一次 Webhook 重试——或者平台重复投递一条消息——才不会造成伤害。

会话还存了 external_user_id,因此一个群聊是一个带发送者归属的会话,而不是每个参与者一个会话。

轮换:从头开始

~new 或切换工作流,绝不能留下两个会话都声称拥有同一个外部聊天。因此轮换在一个事务里做两件事:

  1. 给当前会话的 external_chat_id 加上 __retired_<ts> 后缀,使其退役、不再匹配后续查找。
  2. 创建一个拥有规范映射、并绑定到新工作流的新会话。

任何时刻都不存在两个活跃会话拥有同一外部映射的窗口。测试用的内存实现顺序执行这两步;最坏的交错也只是下一条消息时重新创建会话,而不会产生重复映射。

来自外部渠道的命令

Telegram 或企业微信上的用户没有工具条,所以路由控制以文本命令的形式暴露:

~new                        新建会话
~workflow                   列出工作流(回复数字切换)
~workflow <名称> [消息]      切换工作流,可同时发一条消息
~channel                    列出渠道
~<渠道名> [消息]             切换渠道
~status                     显示当前工作区/工作流
~cancel                     取消正在运行的执行或未完成的选择
~~shortcuts                 列出路由快捷指令(回复数字切换)
~~presets                   列出节点预设(回复数字应用)
~~y                         批准一个待决请求
~~n [原因]                  拒绝一个待决请求
~help                       查看帮助

有两处细节值得知道:

  • 数字回复是有状态的。 ~workflow 列出选项并把这份选择存下来;回复 3 就在这份存下来的列表里解析。显式的 ~workflow <n> 也只针对已存的选择解析,因此没有先列表就直接发数字不会有任何效果。
  • ~<渠道名> 按前缀匹配。 解析器先试整段文本,再试逐步变短的、以空白分隔的前缀,并同时匹配 channel_idname——正因如此,~support 我的订单到哪了 才能一行之内既切渠道又发消息。
  • 审批是无条件的。 ~~y 批准、~~n [原因] 拒绝;有条件审批被明确不支持,会带着提示被拒绝,而不是半执行。

这些命令住在会话命令服务里,而不在摄取管线里,因此外部渠道、CLI 和测试共享同一份实现。

网关 SDK 模型

chengflow-app 网关(代码在 chengflow-sdk/)是一个前端网关层,不是第二个编辑器。它的职责是:

外部应用
  ↓
网关完成配对与连接
  ↓
网关按 channelId 找到应用映射,按 workflowId 决定执行目标
  ↓
网关用用户的 JWT 调用后端
  ↓
后端执行工作流
  ↓
后端返回结果/推送执行事件
  ↓
网关按 channelId + 会话映射把结果转发回来源应用

浏览器网关只用 JWT 鉴权。 它不使用 chk_ 适配器密钥——那属于服务端的平台服务。这是一条硬边界:浏览器前端一旦持有适配器级密钥,就等于把一个能代表整条渠道行事的凭证发了出去。

浏览器里的会话

SDK 的会话管理器持有映射的客户端那一半:

  • 为每个 channelId 生成一个 sessionId(UUID v4)并持久化到 localStorage,因此同一浏览器中的同一应用在刷新后总能恢复同一个会话。
  • sessionId 映射到后端的 external_chat_id,它与 (workspace_id, app_id) 一起唯一确定一个会话。
  • 重置会话会铸造新的 sessionId 并清除关联的会话 id——相当于浏览器里的 ~new
  • 支持同一渠道下多个会话,因此一个应用可以承载多路并行对话。

选择路由形态

你想要 这样做
一个应用,一种行为 在渠道上绑定工作流,完事
一个应用,多种行为 绑定一个按消息内容分支的路由工作流
多个应用,一种行为 建多个渠道,全部绑定同一个工作流
逐回合的路由控制 快捷指令(~~shortcuts,或编辑器工具条)

第四行是最常被忽略的:快捷指令能给单个回合附加结构化路由数据,而不改动渠道的绑定。

下一步

© 版权声明
THE END
喜欢就支持一下吧
点赞11 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容