notice
notice 是一个 Go 多渠道消息通知 package,统一接入飞书、钉钉、企业微信和通用 Webhook。调用方只需要构造统一的 Message,渠道差异、签名和响应业务码均由 package 内部处理。
HTTP 请求统一使用 git.zhangdeman.cn/zhangdeman/network,每次发送只调用一次目标 Webhook,不在 package 内自动重试。
安装
go get git.zhangdeman.cn/zhangdeman/notice
当前版本依赖 Go 1.25 及以上版本。
支持能力
| 消息类型 | 飞书 | 钉钉 | 企业微信 | 通用 Webhook |
|---|---|---|---|---|
| 普通文本 | Text | Text | Text | 统一 JSON |
| 带颜色文本 | 彩色卡片文本 | Markdown 语义前缀 | Markdown 内置颜色 | 统一 JSON |
| Markdown | 交互卡片 | Markdown | Markdown | 统一 JSON |
| 卡片 | Card JSON 2.0 | ActionCard | Template Card | 统一 JSON |
| 卡片主题 | 支持 | 保留内容 | 保留内容 | 原样传递 |
快速开始
创建 Factory
package main
import (
"context"
"os"
"time"
"git.zhangdeman.cn/zhangdeman/notice"
)
func main() {
factory := notice.NewFactory(map[notice.Channel]notice.Config{
notice.ChannelFeishu: {
Webhook: os.Getenv("FEISHU_WEBHOOK"),
Security: ¬ice.SecurityConfig{
SignEnabled: true,
SignSecret: os.Getenv("FEISHU_SIGN_SECRET"),
},
Timeout: 5 * time.Second,
},
notice.ChannelDingTalk: {
Webhook: os.Getenv("DINGTALK_WEBHOOK"),
Security: ¬ice.SecurityConfig{
SignEnabled: true,
SignSecret: os.Getenv("DINGTALK_SIGN_SECRET"),
},
Timeout: 5 * time.Second,
},
notice.ChannelWeCom: {
Webhook: os.Getenv("WECOM_WEBHOOK"),
Timeout: 5 * time.Second,
},
})
sender, err := factory.Get(notice.ChannelFeishu)
if err != nil {
panic(err)
}
if err = sender.Send(context.Background(), notice.Text("服务启动完成")); err != nil {
panic(err)
}
}
Factory.Get(channel) 会缓存初始化成功的 Sender。同一 Factory 内再次获取相同渠道时直接返回已有实例,并发调用也只会初始化一个实例。
也可以绕过 Factory,直接创建单个渠道实例:
sender, err := notice.New(notice.ChannelWebhook, notice.Config{
Webhook: "https://example.com/webhook",
})
Webhook 地址必须为 HTTPS;Timeout 为空时默认 5 秒。
独立签名校验
每个机器人配置拥有独立的安全校验开关和密钥:
Security: ¬ice.SecurityConfig{
SignEnabled: true,
SignSecret: os.Getenv("ROBOT_SIGN_SECRET"),
},
- 飞书:在请求体中加入秒级
timestamp和sign。 - 钉钉:在 Webhook 查询参数中加入毫秒级
timestamp和sign。 SignEnabled为false或未设置Security时不生成签名。- 启用签名后
SignSecret不能为空。 - 企业微信群机器人和通用 Webhook 不启用该签名配置。
SecurityConfig 属于单个渠道的 Config,因此不同机器人可以分别启用签名并使用不同密钥。
消息格式
普通文本
err := sender.Send(ctx, notice.Text("数据同步任务已完成"))
带颜色文本
message := notice.ColorText(
notice.TextSegment{Text: "发布状态:"},
notice.TextSegment{
Text: "失败",
Color: notice.ColorDanger,
Bold: true,
},
)
err := sender.Send(ctx, message)
支持 default、info、success、warning、danger 和 muted 六种语义颜色。各平台会转换为最接近的原生显示效果。
Markdown
err := sender.Send(ctx, notice.Markdown(
"订单服务告警",
"**错误率超过阈值**\n\n当前值:3.2%",
))
卡片消息
message := notice.Card(notice.CardContent{
Title: "发布结果",
Theme: notice.CardThemeGreen,
Markdown: "服务已成功发布到生产环境。",
Fields: []notice.CardField{
{Name: "服务", Value: "order-service"},
{Name: "版本", Value: "v1.8.0"},
},
Actions: []notice.CardAction{
{Text: "查看发布详情", URL: "https://example.com/releases/1001"},
},
})
err := sender.Send(ctx, message)
飞书支持 default、blue、wathet、turquoise、green、yellow、orange、red、carmine、violet、purple、indigo 和 grey 卡片主题。卡片操作地址必须使用 HTTPS。
同步与异步发送
同步发送直接返回结果:
if err := sender.Send(ctx, message); err != nil {
return err
}
异步发送返回容量为 1 的只读错误通道:
result := sender.SendAsync(ctx, message)
go func() {
if err := <-result; err != nil {
log.Printf("send notice failed: %v", err)
}
}()
异步任务运行在当前进程中。调用方需要保证进程和 Context 在发送完成前仍然有效。
包装 Zap Logger
WrapZap 在原 Logger Core 之外附加通知 Core,不改变原有控制台或文件日志输出。
base, err := zap.NewProduction()
if err != nil {
return err
}
logger, err := factory.WrapZap(base, notice.ZapConfig{
Channels: []notice.Channel{
notice.ChannelFeishu,
notice.ChannelDingTalk,
},
OnError: func(err error) {
// 使用独立的处理方式,避免写回 logger 形成递归通知。
fmt.Printf("send log notice failed: %v\n", err)
},
})
if err != nil {
return err
}
defer logger.Sync()
logger.Info("服务启动完成")
logger.Warn("订单延迟升高", zap.Int("delay_ms", 2500))
logger.Error("订单创建失败", zap.String("order_id", "O1001"))
Levels 为空时,仅精确匹配 WarnLevel 和 ErrorLevel。自定义等级示例:
logger, err := factory.WrapZap(base, notice.ZapConfig{
Channels: []notice.Channel{notice.ChannelWeCom},
Levels: []zapcore.Level{
zapcore.ErrorLevel,
zapcore.DPanicLevel,
},
})
通知异步发送到指定渠道。logger.Sync() 会等待已触发的通知结束,适合在程序退出前调用。日志消息会转换为卡片,包含等级、Logger 名称、时间、调用位置以及 Zap 字段。
错误处理
- 参数错误可通过
errors.Is判断ErrUnsupportedChannel、ErrChannelNotConfigured、ErrInvalidConfig、ErrInvalidMessage和ErrInvalidZapConfig。 - 非 2xx 响应返回
*notice.HTTPError。 - 平台业务码失败返回
*notice.PlatformError。 SendAsync的错误通过返回通道读取;Zap 通知错误通过ZapConfig.OnError接收。
var platformErr *notice.PlatformError
if errors.As(err, &platformErr) {
log.Printf("channel=%s code=%s message=%s", platformErr.Channel, platformErr.Code, platformErr.Message)
}
配置中包含 Webhook 和签名密钥,业务代码不要将完整配置写入日志。
开发验证
go test -race ./...
详细的协议映射与实现说明见多渠道消息通知平台设计文档。