messengers(消息通知)模块的架构。
Yao 的消息系统采用了 “通道 (Channel) - 提供商 (Provider) - 模板 (Template)” 的三层解耦设计。这种设计允许开发者灵活切换底层服务商(如从 SMTP 切换到 SendGrid),而无需修改业务逻辑代码。
以下是详细的技术分析:
1. 目录结构与配置层分析 (yao-dev-app)
在 yao-dev-app 中,messengers 目录是消息服务的定义中心。
目录树结构
text
yao-dev-app/messengers/
├── channels.yao // [核心] 通道定义,将逻辑名称映射到具体 Provider
├── providers/ // [底层] 具体服务商的连接配置
│ ├── primary.mailer.yao // 例如:SMTP 邮件服务
│ ├── unified.twilio.yao // 例如:Twilio 短信服务
│ └── ...
└── templates/ // [内容] 消息内容模板
├── en/ // 英文模板目录
│ ├── verify_email.mail.html // 邮件模板
│ └── verify_mobile.sms.txt // 短信模板
└── zh-cn/ // 中文模板目录
└── ...A. 提供商 (Provider)
位于 messengers/providers/。这是与外部服务(SMTP, Twilio, Mailgun 等)通讯的物理配置。
- 配置示例 (
providers/primary.mailer.yao):json{ "name": "Primary Mailer", "type": "mailer", // 对应引擎中的加载器类型 "options": { "host": "$ENV.MAIL_HOST", // 支持环境变量 "port": "$ENV.MAIL_PORT", "username": "$ENV.MAIL_USERNAME", "password": "$ENV.MAIL_PASSWORD" } } - 作用:定义 “怎么发”。它封装了协议细节和凭证。
B. 通道 (Channel)
位于 messengers/channels.yao。这是业务层使用的逻辑名称。
- 配置示例:json
{ "default": "primary", // 逻辑名 "default" -> 映射到 provider ID "primary" "sms": "unified", // 逻辑名 "sms" -> 映射到 provider ID "unified" "marketing": "marketing" } - 作用:定义 “用谁发”。业务代码(如注册接口)只引用 "default" 或 "sms" 通道,而不关心底层是 SMTP 还是 API。
C. 模板 (Template)
位于 messengers/templates/。Yao 使用文件系统路径来解析模板 ID。
- 命名规则:
<语言>/<名称>.<类型>.<扩展名>- 例如
en/verify_email.mail.html - Template ID:
en.verify_email(在调用时使用) - 类型:
mail(对应邮件类型)
- 例如
- 内容: 支持 Go
text/template语法,例如{{ .code }}用于插入验证码。
2. 引擎层处理逻辑分析 (yao)
Yao 引擎的 messenger 包负责加载配置并执行发送逻辑。
A. 加载逻辑 (Loading Logic)
在 yao/messenger/messenger.go 中:
- 加载 Provider: 引擎扫描
messengers/providers/*.yao,根据文件后缀和内容初始化具体的客户端(如mailer,twilio等)。 - 加载 Channel: 解析
messengers/channels.yao,建立Channel Name -> Provider的映射关系。 - 加载 Template: 引擎会扫描
messengers/templates目录,将文件内容加载到内存中,并预编译为 Go Template 对象。
B. 发送逻辑 (Sending Flow)
当业务层(如用户注册)调用发送接口时,流程如下:
关键代码分析 (yao/messenger/messenger.go):
SendT函数: 这是发送模板消息的核心入口。gofunc (m *Messenger) SendT(ctx context.Context, channel, template string, data map[string]interface{}, messageType ...interface{}) error { // 1. 获取 Provider provider := m.getProvider(channel) // 2. 渲染模板内容 // 这里会根据 template ID 找到对应的文件内容,并用 data 进行渲染 content, err := m.render(template, data) // 3. 调用 Provider 的 Send 方法 // 不同的 Provider (Mailer, Twilio) 实现了统一的 Send 接口 return provider.Send(ctx, content, messageType...) }
C. 验证码发送的具体实现
结合之前的 openapi/user/entry.go 分析:
- 触发:用户请求
/entry/verify。 - 配置读取:读取
openapi/user/entry/en.yao中的messenger配置:json"messenger": { "mail": { "channel": "default", // 指向 channels.yao 中的 "default" "template": "en.verify_email" // 指向 templates/en/verify_email.mail.html } } - 执行:
- Yao 后端调用
messenger.Instance.SendT(...)。 data参数包含了生成的 OTPcode。- 模板中的
{{ code }}被替换。 - 最终通过
primary.mailer.yao配置的 SMTP 服务器发出邮件。
- Yao 后端调用
3. 总结
Yao 的消息系统配置逻辑具有高度的工程化特征:
- 多语言原生支持:通过目录结构 (
en/,zh-cn/) 直接管理多语言模板,无需在代码中通过if-else判断语言。 - 运行时动态替换:你可以通过修改
channels.yao将sms通道从twilio切换到aliyun,而无需重启服务或修改任何业务代码(基于热加载机制)。 - 标准化接口:无论是邮件、短信还是 WebHook,在 Go 代码层面都抽象为统一的
Send接口,这使得扩展新的消息类型(如钉钉、飞书)变得非常容易。
如果你需要新增一种消息通知(例如接入 Slack),你只需要在 providers/ 下新增配置文件,在 channels.yao 中注册,并添加对应的模板即可。