模板渲染前必须预编译,否则并发调用 execute 会因 html/template 非线程安全触发 concurrent map writes panic;应初始化时用 template.parsefiles 或 template.new(...).parse(...) 构建复用实例,html 输出用户数据须用 {{.field | html}} 过滤,异步发送需用缓冲队列+工作池+指数退避重试。

模板渲染前必须预编译,否则并发发送时 panic
Go 的 html/template 包不是线程安全的——如果在多个 goroutine 中直接调用 template.Execute 且模板未预编译,会触发 concurrent map writes panic。正确做法是提前调用 template.ParseFiles 或 template.New(...).Parse(...) 构建复用的 *template.Template 实例,再在异步任务中只调用 Execute。
- 不要在每个邮件发送 goroutine 里重复
template.ParseFiles("email.html") - 推荐在应用初始化阶段一次性加载:
mailTmpl := template.Must(template.ParseFiles("templates/welcome.html")) - 若需多模板(如欢迎信、重置密码),用
template.New("welcome").ParseFiles(...)分开命名,避免冲突
HTML 模板里禁止直接输出用户输入,必须用 {{.Body | html}} 过滤
邮件内容常含用户提交的昵称、留言等字段。若直接写 {{.UserInput}},攻击者可注入恶意 HTML 或 JavaScript(尽管多数邮件客户端会过滤 script,但 img src=javascript: 等仍可能触发行为)。html/template 默认已做转义,但仅对变量插值生效;若你用 {{.RawHTML}} 且该字段来自用户,则必须显式加 | html 安全过滤器。
- 安全写法:
{{.Message | html}}(即使.Message是纯文本也无副作用) - 危险写法:
{{.Message}}(当.Message含<script></script>时会被原样渲染) - 注意:
text/template不提供| html,必须用html/template
异步发送不能用简单 goroutine,要配缓冲队列和错误重试
直接写 go sendMail(...) 看似异步,但若瞬间涌进 1000 封邮件,会创建 1000 个 goroutine,极易耗尽内存或打爆 SMTP 服务器连接数。更稳妥的是用带缓冲的 channel + 工作池模式,并对临时失败(如网络超时)做指数退避重试。
- 定义通道:
mailQueue := make(chan MailTask, 100)(缓冲大小根据内存和峰值预估) - 启动固定数量 worker:
for i := 0; i - 发送失败时记录日志并重入队列(最多 3 次),避免静默丢信
- SMTP 客户端建议用
gopkg.in/gomail.v2,它支持设置SetDialer和SetTimeout
Gmail 或企业邮箱发信需启用 OAuth2 或 App Password,普通密码会失败
2024 年起 Gmail 已全面禁用“用户名+密码”登录 SMTP(报错 535 5.7.8 Username and Password not accepted)。即使你代码里填了正确密码,也会被拒绝。必须改用 App Password(开启两步验证后生成)或 OAuth2 token。
- Gmail 用户:进入 Google 账户 → 安全 → 两步验证 → App passwords,生成 16 位密钥代替密码
- 企业邮箱(如 Outlook/Office365):同样需在管理后台开启现代身份验证,或配置应用密码
- 测试时可用
smtp.gmail.com:587+ App Password,但生产环境建议用专用邮件服务(如 SendGrid、Mailgun)API,更稳定且自带模板和追踪
user.Name,模板里却写了 {{.UserName}},这种错不会编译报错,只会让邮件内容为空白,且日志里很难定位。建议在模板渲染后加一行 if err != nil { log.Printf("tmpl exec failed: %v", err) },并把渲染结果先写入 bytes.Buffer 打印调试。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











