统一消息结构体需包含channel、priority、retry_times及嵌套ChannelData字段,用template_key查模板而非硬编码内容,避免扩展时反复修改结构。

Beego 本身不内置多渠道消息推送能力,但可以快速集成短信、邮件、微信模板消息、站内信等通道——关键在于路由分发逻辑、异步任务调度和统一消息结构的设计,而不是框架是否“原生支持”。
如何设计统一的消息结构体
所有渠道最终都要走 MsgController 或独立的 NotifyService,必须先定义可扩展的消息模型。硬编码字段(如 to_phone、template_id)会导致后续加飞书/钉钉时反复改结构。
- 用
map[string]interface{}或结构体嵌套ChannelData字段承载渠道特有参数,例如微信需要openid和template_id,短信只需phone和content - 必须包含
channel字段(值为"sms"、"email"、"wechat"等),用于后续路由分发 - 建议加上
priority和retry_times,方便对接任务队列(如asynq或machinery) - 不要在结构体里放 HTML 模板或长文本内容,应通过
template_key查配置中心或数据库
Beego 中如何做渠道路由分发
别在控制器里写 if-else 判断 channel 类型。Beego 的 GlobalControllerRouter 是全局映射,但更适合 HTTP 路由;消息分发应走服务层抽象。
- 建一个
notify/router.go,定义func Dispatch(msg *NotifyMessage) error,内部用 map[string]func(*NotifyMessage) error 注册各渠道处理器 - 每个渠道实现为独立包(
notify/sms、notify/wechat),避免耦合;微信部分可复用beego.Controller的GetSession获取用户 openid 缓存,但不要直接依赖 Controller 实例 - 若需按用户偏好自动选渠道(比如用户设置了“重要通知推微信,普通通知发站内信”),把偏好查出来后塞进
msg.Channel再 dispatch,而非在 dispatch 里查库 - 错误要分类:网络超时可重试,模板 ID 不存在应立即失败并告警
为什么不能直接在 Controller 里调用 SendSMS()?
常见错误是把发送逻辑写死在 MsgController.Send() 方法里,导致三个问题:接口响应慢、无法重试、日志难追踪。
- HTTP 请求必须短平快,
SendSMS()这类 IO 密集操作应丢进 goroutine 或任务队列,Controller 只返回{"task_id": "ntf_abc123"} - Beego 的
context.Input.Data不跨 goroutine 生效,别试图在子协程里用c.Ctx.Input.IP()记日志 - 如果用微信模板消息,
access_token需缓存并自动刷新——这逻辑绝不能放在每次请求的 Controller 里,应抽成单例服务(wechat.TokenManager) - 测试困难:Controller 单元测试会因真实 API 调用而失败,应 mock 掉整个
notify.Dispatch接口
WebSocket 推送和 HTTP 推送怎么共存?
站内信、实时通知类消息常需双写:存 DB + 推 WebSocket。Beego 原生支持 WebSocket,但别让 NotifyService 直接操作 *websocket.Conn。
- 用 Beego 的
beego.BeeApp.Handlers或自定义全局 map 存在线连接(key 为 user_id),但注意并发安全——用sync.Map,别用普通 map - WebSocket 推送失败(如连接已断)不应阻塞 DB 写入,DB 成功即算“推送成功”,断连重连逻辑由前端心跳机制处理
- 不要在
MsgController里调c.WebSocketWrite(),而应触发事件(如event.Publish("notify.user.123", msg)),由单独的 event listener 处理 WebSocket 分发 - 若用户同时登录多端,需在 DB 记录设备 ID,推送时遍历该用户的全部有效连接,而不是只推最新一个
最易被忽略的是消息幂等性:同一业务事件(如支付成功)可能触发多次通知任务。必须在任务入队前加唯一键(如 pay_success:order_789),用 Redis SETNX 或数据库唯一索引拦截重复。Beego 不管这个,得你亲手加。


















