webhook 入口应轻量、可监控、易调试:用 net/http.handlefunc 实现,仅校验签名、读 body、异步丢入 channel;渠道解耦靠工厂方法动态创建 messagesender 实例;token 需刷新机制;消息分发须带缓冲 chan 和 worker 熔断。

用 net/http 搭 Webhook 入口,别直接暴露业务逻辑
通知系统的第一道门必须是轻量、可监控、易调试的 HTTP 接口,不是直接在 Jenkins 或 Alertmanager 里硬编码调用 URL。否则改个渠道就得动上游,出问题连日志都难追。
-
http.HandleFunc足够,别一上来就上 Gin/echo——多一层框架就多一层 panic 风险和上下文泄漏可能 - 入口函数里只做三件事:读
r.Body、校验签名(比如X-Hub-Signature-256)、丢进 channel 异步处理;任何解析失败或字段缺失,统一返回400 Bad Request并记 error 日志 - 别在 handler 里直接调企业微信 API——网络超时会卡死整个 HTTP 连接池;
go notifier.Send(...)是底线 - 如果上游没加签名(如某些老旧 CI 工具),至少用
IP 白名单+BasicAuth拦住扫描器,别裸奔
用工厂方法注册渠道,别写 if-else 判断 channel 类型
加个钉钉支持就加一个 if channel == "dingtalk",再加邮件又加一个 else if——这种代码半年后没人敢动,一改就崩。Go 里解耦渠道的核心不是接口,是“谁来造这个接口的实例”。
- 定义
type MessageSender interface { Send(ctx context.Context, msg Message) error },每个渠道实现自己的结构体(如DingTalkSender、WeComSender) - 工厂方法不写死:
func NewSender(cfg config.ChannelConfig) (MessageSender, error),根据cfg.Type返回对应实例;启动时从app.yaml读配置,动态注册 - 注意
access_token管理:企微/钉钉 token 必须带刷新逻辑,别在NewWeComSender里一次性取完就缓存全局——要用sync.RWMutex包裹 token 字段,或直接上redis缓存+过期时间 - 测试时容易漏:不同渠道对
message.Title长度、message.Content的格式要求完全不同(钉钉卡片不认换行符,企微外部群不支持图片 base64),别共用一套模板
用 channel + goroutine 做异步分发,但得设缓冲和熔断
通知不是强一致场景,但也不能无脑 go send() ——瞬间几百告警进来,没控制的 goroutine 会把内存打穿,还可能触发企微/钉钉的频控限流,返回 429 Too Many Requests 却没重试。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 用带缓冲的
chan Message(比如make(chan Message, 1000)),配合固定数量 worker goroutine 消费(如for i := 0; i ) - 每个 sender 实现里必须有重试:HTTP 错误且状态码是
429或5xx时,用time.AfterFunc延迟 30 秒重入队列;别用无限 for 循环重试,会卡死 goroutine - 加简单熔断:连续 5 次发送失败(不管啥错误),自动标记该渠道为
unhealthy,10 分钟内跳过所有发往它的消息,并发告警到运维群 - 别把日志塞进 channel——
log.Printf是同步 IO,会拖慢整个管道;只记录关键 traceID 和失败原因,其余丢给 ELK 或 Loki
Redis Pub/Sub 做跨进程通知,别用本地 channel 同步
单机跑没问题,但一旦服务拆成多个实例(比如一个收 Webhook,一个查数据库,一个发消息),本地 chan 就彻底失效。这时候 Redis 不是“可选”,是必选项。
- 管理机(Webhook 服务)用
rdb.Publish(ctx, "notify:topic", payload)发布;接口机用rdb.Subscribe(ctx, "notify:topic")监听——别自己实现 TCP 广播,那是在重复造轮子且不可靠 - 频道名带环境前缀:
"notify:prod"和"notify:staging"必须隔离,否则测试流量会炸掉生产群 - payload 用 JSON,但别嵌套太深:Alertmanager 来的原始数据可以保留,但要扁平化提取
severity、alertname、instance字段,避免每次消费都要json.Unmarshal大对象 - Redis 连接别复用
http.Client那套——用redis.NewClient单例,设置PoolSize: 20,并启用MinIdleConns防冷启延迟
最常被跳过的其实是凭证刷新和错误归因:token 过期了,是静默失败还是触发熔断?429 是因为并发太高,还是配置里把 chat_id 写错了?这些细节不埋点,线上出问题只能靠猜。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










