邮箱校验应优先用 net/mail.ParseAddress 而非正则,它能正确解析带引号昵称等合法格式;验证码必须用 crypto/rand 生成以保证安全;Gmail/Outlook 发信需应用专用密码配合 smtp.PlainAuth;验证码存储推荐 Redis 并设 TTL,避免内存泄漏与分布式失效。
net/mail.ParseAddress 还是正则?直接用正则匹配邮箱是常见误区。Go 标准库的 net/mail.ParseAddress 更可靠,它会尝试解析邮箱地址结构(如 "name@example.com" 或带引号的昵称),而不仅是字符串模式。但注意:它不验证域名是否存在或 MX 记录是否有效,仅做语法解析。
实际建议分两步:
net/mail.ParseAddress 检查基本格式和本地部分合法性(避免空格、控制字符等)net.LookupMX 查 MX 记录——这一步可选,但能筛掉大量无效域名(如 @gmail.con)^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$),它会放过 "test@.com" 或漏掉合法的 "user+tag@example.org"
crypto/rand 还是 math/rand?必须用 crypto/rand。验证码本质是安全凭证,math/rand 是伪随机、可预测,攻击者只要知道 seed 就能复现全部验证码序列。
生成示例:
func generateCode() string {
b := make([]byte, 6)
if _, err := crypto/rand.Read(b); err != nil {
panic(err) // 实际应返回 error
}
for i := range b {
b[i] = byte('0' + b[i]%10)
}
return string(b)
}注意点:
time.Now().UnixNano() 做 seed 初始化 math/rand —— 时间精度有限,高并发下极易重复rand.Intn(10)),每次调用都需独立读取 crypto/rand
rand.Intn(62) 直接映射,防止分布偏差net/smtp.Auth 怎么配 Gmail / Outlook?Gmail 和 Outlook 已禁用“密码登录”,必须用应用专用密码(Gmail)或 OAuth2(推荐但复杂)。开发阶段最简路径是开启 Gmail 的“两步验证 + 应用专用密码”:
"smtp.gmail.com:587",认证方式用 smtp.PlainAuth
"user@gmail.com"),密码填应用专用密码,不是账户密码"smtp-mail.outlook.com:587",同样需在 Microsoft 账户中开启应用密码代码片段:
auth := smtp.PlainAuth("", "user@gmail.com", "abcd efgh ijkl mnop", "smtp.gmail.com")
err := smtp.SendMail("smtp.gmail.com:587", auth, "user@gmail.com", []string{"to@example.com"}, msg)常见错误:
535 5.7.8 Username and Password not accepted:密码错、未开两步验证、或用了账户密码而非应用密码dial tcp: lookup smtp.gmail.com: no such host:DNS 问题或代理干扰,可换用 1.1.1.1 测试From 和 Reply-To 域名一致,且 SPF/DKIM 记录已配置(生产环境必需)开发阶段可用 sync.Map 存储验证码,但必须配 TTL 清理逻辑。直接存 map 不设过期会导致内存泄漏,且无法跨进程共享(部署多个实例时失效)。
更稳妥的做法:
time.Now().Add(10 * time.Minute) 计算过期时间,写入结构体,读取时先比对 time.Now().Before(expiry)
SET key code EX 600 命令,天然支持自动过期,且支持分布式验证sha256(code + salt))用于比对容易被忽略的是时区和系统时间漂移:所有过期判断必须基于服务端统一时间,不能依赖客户端传来的“当前时间”。