多渠道重构规划:桥接 + 策略 + 模板方法,把“发消息”变成可扩展结构

2026年1月26日
2 分钟阅读
作者:ixNieStudio

多渠道重构规划:桥接 + 策略 + 模板方法,把“发消息”变成可扩展结构

我准备把 edgeone-webhook-pusher 的“推送”这块,做一次结构性升级

  • 第一阶段只做一件事:重构架构,但确保现有微信公众号推送完全不回归
  • 第二阶段再做一件事:在新架构上加上 企业微信渠道

这不是为了“用设计模式显得高级”,而是因为我已经预见到一种必然会发生的工程灾难:

渠道越多,push.service 里的 if/else 就越长; 越长越没人敢动,最后每加一个渠道都要冒着把老渠道搞挂的风险。

所以这次我想把结构先定住:桥接定结构,策略做选择,模板方法管流程。

先把“渠道”分成两类(这是结构的第一刀)

这套系统里,渠道其实不是同一种东西。

  • Token 管理型(复杂):微信、企业微信
    • 要拿 access_token
    • 要处理过期 / 刷新 / 重试
    • 要在 KV 里维护 token 状态
  • Webhook 转发型(简单):钉钉、飞书、自定义 webhook
    • 基本就是拼 payload + POST
    • 不需要 token、也不需要目标管理

这意味着:如果我把它们都抽象成一个“万能 Channel”,接口一定会变得虚伪。

所以我选择在接口层就把能力分开。

flowchart TB P["push.service
编排"] --> SEL["StrategySelector
运行时选择"] SEL --> S1[wechat strategy] SEL --> S2[enterprise-wechat strategy] SEL --> S3[dingtalk strategy] SEL --> S4[feishu strategy] S1 --> T1[TokenManagedChannelService] S2 --> T1 S3 --> W1[WebhookChannelService] S4 --> W1 T1 --> TMP1["TokenSendTemplate
固定 5 步"] W1 --> TMP2["WebhookSendTemplate
固定 4 步"]

桥接:让 Channel 只管“怎么发”,让 App 只管“发给谁”

我在既有模型里遇到的一个痛点是:

  • Channel 里有平台凭证(appSecret/corpSecret),这是“怎么发”的问题
  • App 里有业务配置(pushMode/messageType/targets),这是“发给谁/怎么组织内容”的问题

但在实际代码里,这两者很容易互相污染:

  • 渠道实现开始关心“目标列表怎么拿”
  • 应用实现开始关心“token 怎么刷”

桥接模式的出发点是:结构上就不允许它们互相侵入

  • Channel 提供能力:token / send
  • App 提供配置:目标 / 消息类型 / 推送模式

这样未来扩一个新渠道时:

  • 你只改“渠道能力实现”
  • 或者只改“应用配置与目标管理实现”

而不是两边一起改。

策略:运行时选择“用谁来发”(而不是 if/else)

策略模式在这里非常朴素:

  • 维护一个 channelType -> strategyFactory 的注册表
  • push() 里根据 channel.type 选择策略

策略只负责“选谁”,不要在策略里再写平台差异,否则会退化成 if/else 的搬家。

模板方法:把发送流程锁死(避免每个渠道都写一套流程)

真正能把工程复杂度压住的,是模板方法。

我希望所有 Token 型渠道(微信/企业微信)都遵守同一套流程:

  1. 参数校验
  2. 获取/刷新 token
  3. 构建消息
  4. 发送
  5. 解析返回

差异只能出现在“抽象方法”里,比如:

  • token 怎么拿
  • message payload 长什么样
  • API endpoint 是什么

这样未来你再加“飞书(token 型)”也不会复制粘贴一套流程。

sequenceDiagram autonumber participant PS as push.service participant ST as Strategy participant TMP as TokenSendTemplate participant CS as ChannelService participant API as Platform API PS->>ST: select(channelType) ST->>TMP: executeSendFlow(input) TMP->>TMP: 1 validateParams TMP->>CS: 2 getAccessToken TMP->>CS: 3 buildMessage TMP->>API: 4 doSend TMP->>TMP: 5 parseResult TMP-->>PS: PushResult

渐进式落地:先保微信,再加企业微信

这份规划里我给自己定了一个很硬的约束:

  • 架构重构必须先落地(哪怕先只支持两类渠道:微信 + 企业微信)
  • 测试可以延后(MVP 先可用),但要保留旧实现做兜底

阶段一完成的标志不是“代码更优雅”,而是:

  • 仍然可以用原来的方式创建微信 Channel/App
  • /send/{appKey} 的行为不变
  • 错误语义、消息历史落库逻辑不变

阶段二再把企业微信作为“第一个真正检验扩展性”的新渠道接进来。

启发:设计模式在这里不是装饰,而是“让边界变硬”

我现在越来越相信一件事:

  • 当你把系统的边界写成“口头约定”,它总会被打破。
  • 当你把边界写成“类型 + 接口 + 结构”,它才真的存在。

这也是为什么我宁愿在一开始多写一点抽象层,也不想把未来的扩展点埋进一堆 if/else 里。