应使用 time.Ticker 配合 context.WithTimeout、recover、ticker.Stop() 和首次 time.AfterFunc 实现健壮定时任务,结合 text/template 渲染日报、HTTP Webhook 发送、可 mock 的时间函数及完备错误处理与日志。

用 time.Ticker 实现基础定时循环,但别直接裸跑
Go 里最简单的定时方案就是 time.Ticker,但它本身不处理任务并发、失败重试或优雅退出。裸用 ticker := time.NewTicker(24 * time.Hour) 启动后,如果日报生成逻辑 panic 或阻塞,整个 ticker 就卡死,下次触发永远不来。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
select+context.WithTimeout包裹单次执行,避免某次失败拖垮后续调度 - 启动前加
go func() { defer recover() }()捕获 panic,防止 goroutine 泄漏 - 程序退出时调用
ticker.Stop(),否则可能残留 goroutine(尤其在测试或 SIGINT 场景) - 首次执行建议用
time.AfterFunc单独触发,避开“启动即发”还是“等满 24h 才发”的歧义
日报内容生成别硬编码模板,用 text/template 动态渲染
硬拼字符串发日报,改格式就得改代码;用 text/template 把结构和数据分离,维护成本低得多。注意它默认不转义 HTML,发邮件时要小心 XSS(虽然日报通常纯文本),发企业微信/钉钉则需按平台要求调整换行符。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 模板文件单独存为
report.tmpl,用template.ParseFiles("report.tmpl")加载 - 传入数据结构必须是导出字段(首字母大写),比如
type ReportData struct { Date string; Items []string } - 模板里用
{{.Date}}、{{range .Items}}{{.}}\n{{end}},避免手动拼接换行 - 调试时先用
bytes.Buffer渲染并打印结果,确认格式无误再对接发送逻辑
发通知优先走 HTTP API,别碰 SMTP 配置
自建 SMTP 服务器要配 TLS、认证、退信处理,还容易被当成垃圾邮件。现在主流办公平台(钉钉、飞书、企业微信)都提供简单易用的 Webhook HTTP 接口,POST 一个 JSON 就能发消息,开发和运维负担小很多。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 钉钉机器人需开启“自定义关键词”或“加签”,否则请求会被拒,错误信息是
{"errcode":101,"errmsg":"sign error"} - 企业微信用
https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx,body 是{"msgtype":"text","text":{"content":"..."}} - HTTP 客户端务必设超时:
http.Client{Timeout: 10 * time.Second},不然网络抖动会导致 ticker 阻塞 - 发完检查
resp.StatusCode == 200和响应体里的errcode字段(如钉钉返回"errcode":0才算成功)
本地测试时用 time.Now().Add 模拟时间推进
等真实 24 小时测一次?不可能。用系统时间 mock 是最轻量的方案,不需要引入第三方库或改系统时钟。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 把业务时间相关逻辑抽成函数,接收
time.Time参数,例如func genReportDate(now time.Time) string { return now.Format("2006-01-02") } - 测试时传入
time.Date(2024,1,1,9,0,0,0,time.UTC).Add(24*time.Hour)验证跨日逻辑 - 避免在代码里直接调用
time.Now(),否则无法控制测试边界 - CI 环境里注意时区:Docker 默认 UTC,而日报常需本地时区,用
time.Now().In(loc)显式指定
真正难的不是定时或发消息,而是当某天网络不通、模板字段缺失、API 返回 503 时,你的模块有没有留下可查的日志、是否跳过本次而不影响下次、失败后要不要告警——这些细节不会出现在“快速搭建”里,但决定了它能不能真用起来。


















