开通POP3/SMTP/IMAP服务
代码部分
安装email
go get github.com/jordan-wright/email
yaml配置
email:
account: 邮箱账号
password: 授权码
host: smtp.163.com
port: 25
# 授权类型,163用这个OK,我outlock用的自定义的login
auth: plain
初始化连接池,可以参考这个文档
https://www.topgoer.com/%E5%85%B6%E4%BB%96/%E5%8F%91%E9%82%AE%E4%BB%B6.html
initialize.go
package initilize
import (
"crypto/tls"
"fmt"
"log"
"net/smtp"
"xxx/dmp/demo/common/global"
"github.com/jordan-wright/email"
)
/**
* 初始化邮件客户端
*/
func InitilizeEmail() {
// 创建连接池
p, err := email.NewPool(
fmt.Sprintf("%s:%d", global.CONFIG.Email.Host, global.CONFIG.Email.Port),
4,
smtp.PlainAuth("", global.CONFIG.Email.Account, global.CONFIG.Email.Password, global.CONFIG.Email.Host),
&tls.Config{
InsecureSkipVerify: true, // 忽略证书验证(慎用)
ServerName: global.CONFIG.Email.Host,
},
)
if err != nil {
log.Fatal("创建邮箱客户端连接池失败:", err)
}
global.EmailPool = p
fmt.Println("创建邮箱客户端连接池成功")
}
代码封装
loginAuth.go
package email
import (
"errors"
"net/smtp"
"xxx/common/global"
)
type loginAuth struct {
username, password string
}
func LoginAuth(username, password string) smtp.Auth {
return &loginAuth{username, password}
}
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
return "LOGIN", []byte{}, nil
}
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if more {
switch string(fromServer) {
case "Username:":
return []byte(a.username), nil
case "Password:":
return []byte(a.password), nil
default:
return nil, errors.New("unkown fromServer")
}
}
return nil, nil
}
func GetAuth() smtp.Auth {
switch global.CONFIG.Email.Auth {
case "login":
return LoginAuth(global.CONFIG.Email.Account, global.CONFIG.Email.Password)
case "cramMD5":
return smtp.CRAMMD5Auth(global.CONFIG.Email.Account, global.CONFIG.Email.Password)
default:
return smtp.PlainAuth("", global.CONFIG.Email.Account, global.CONFIG.Email.Password, global.CONFIG.Email.Host)
}
}
email.go
package email
import (
"crypto/tls"
"errors"
"fmt"
"os"
"sync"
"xxx/common/global"
"xxx/util"
"github.com/gin-gonic/gin"
"github.com/jordan-wright/email"
)
const (
// 邮箱绑定
EmailTypeBind = 1
)
var topics = map[int]string{
EmailTypeBind: "邮箱绑定",
}
var templates = map[int]string{
EmailTypeBind: "bind_email.tmpl",
}
var emailServiceRegistry = struct {
services map[int]*EmailService
sync.Mutex
}{
services: make(map[int]*EmailService),
}
var background, logo, floor string
func init() {
background, _ = util.ImageToBase64("./static/email/background.png")
logo, _ = util.ImageToBase64("./static/email/logo.png")
floor, _ = util.ImageToBase64("./static/email/floor.png")
}
type EmailService struct {
Email *email.Email
TemplateContent []byte
}
func GetEmailInstance(t int) (*EmailService, error) {
topic, ok := topics[t]
temp, ok2 := templates[t]
if !ok || !ok2 {
return nil, errors.New("主题不存在")
}
emailServiceRegistry.Lock()
defer emailServiceRegistry.Unlock()
// 检查注册树中是否已经存在对应类型的 EmailService
if service, exists := emailServiceRegistry.services[t]; exists {
return service, nil
}
e := email.NewEmail()
e.From = "System <" + global.CONFIG.Email.Account + ">"
e.Subject = topic
// 读取模板文件内容
templateFile := "./client/email/template/" + temp
templateContent, err := os.ReadFile(templateFile)
if err != nil {
return nil, errors.New("模板不存在")
}
service := &EmailService{
Email: e,
TemplateContent: templateContent,
}
// 将创建的 EmailService 注册到注册树中
emailServiceRegistry.services[t] = service
return service, nil
}
func (e *EmailService) Send(to []string, data any) error {
// 解析模板
tmpl, err := util.GenerateHTMLTemplate(string(e.TemplateContent), data)
if err != nil {
return err
}
e.Email.To = to
e.Email.HTML = tmpl.Bytes()
return e.Email.SendWithStartTLS(fmt.Sprintf("%s:%d", global.CONFIG.Email.Host, global.CONFIG.Email.Port), GetAuth(),
&tls.Config{
InsecureSkipVerify: true, // 忽略证书验证(慎用)
ServerName: global.CONFIG.Email.Host,
})
// 使用连接池会报错 read tcp xx.xx.xx.xx:xx->xx.xx.xx.xx:xx: read: connection reset by peer
// 是的,您需要设置重试 - 在这种情况下,远程服务器会重置连接,这可能表明存在防火墙、垃圾邮件过滤器或任何其他问题。
// return global.EmailPool.Send(e.Email, 30*time.Second)
}
type PublicData struct {
FloorImg string
BackgroundImg string
LogoImg string
}
// 绑定
type BindEmailData struct {
PublicData
Code string
UserName string
ValidTime int
}
func GetPublicData() PublicData {
return PublicData{
FloorImg: "data:image/png;base64," + floor,
BackgroundImg: "data:image/png;base64," + background,
LogoImg: "data:image/png;base64," + logo,
}
}
func SendBindEmail(ctx *gin.Context, to string, userName string, code string, validTime int) error {
instance, err := GetEmailInstance(EmailTypeBind)
if err != nil {
return err
}
return instance.Send([]string{to}, BindEmailData{
PublicData: GetPublicData(),
Code: code,
UserName: userName,
ValidTime: validTime,
})
}
自定义模板
bind_email.tmpl
<html>
<body>
<h1>验证码: {{ .Code }}</h1>
</body>
</html>